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.app.FragmentManager; 20 import android.app.ProgressDialog; 21 import android.content.Context; 22 import android.os.Bundle; 23 import android.support.v17.leanback.app.GuidedStepFragment; 24 import android.support.v17.leanback.widget.GuidanceStylist.Guidance; 25 import android.support.v17.leanback.widget.GuidedAction; 26 import android.support.v17.leanback.widget.GuidedActionsStylist; 27 import android.util.Log; 28 import android.util.LongSparseArray; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.widget.FrameLayout; 32 import android.widget.ProgressBar; 33 34 import com.android.tv.R; 35 import com.android.tv.TvApplication; 36 import com.android.tv.data.Channel; 37 import com.android.tv.data.ChannelDataManager; 38 import com.android.tv.data.Program; 39 import com.android.tv.dvr.DvrDataManager; 40 import com.android.tv.dvr.DvrManager; 41 import com.android.tv.dvr.DvrUiHelper; 42 import com.android.tv.dvr.EpisodicProgramLoadTask; 43 import com.android.tv.dvr.SeriesRecording; 44 import com.android.tv.dvr.SeriesRecording.ChannelOption; 45 import com.android.tv.dvr.SeriesRecordingScheduler; 46 import com.android.tv.dvr.SeriesRecordingScheduler.OnSeriesRecordingUpdatedListener; 47 import java.util.ArrayList; 48 import java.util.Comparator; 49 import java.util.HashSet; 50 import java.util.List; 51 import java.util.Set; 52 53 /** 54 * Fragment for DVR series recording settings. 55 */ 56 public class SeriesSettingsFragment extends GuidedStepFragment 57 implements DvrDataManager.SeriesRecordingListener { 58 private static final String TAG = "SeriesSettingsFragment"; 59 private static final boolean DEBUG = false; 60 61 private static final long ACTION_ID_PRIORITY = 10; 62 private static final long ACTION_ID_CHANNEL = 11; 63 64 private static final long SUB_ACTION_ID_CHANNEL_ALL = 102; 65 // Each channel's action id = SUB_ACTION_ID_CHANNEL_ONE_BASE + channel id 66 private static final long SUB_ACTION_ID_CHANNEL_ONE_BASE = 500; 67 68 private DvrDataManager mDvrDataManager; 69 private ChannelDataManager mChannelDataManager; 70 private DvrManager mDvrManager; 71 private SeriesRecording mSeriesRecording; 72 private long mSeriesRecordingId; 73 @ChannelOption int mChannelOption; 74 private Comparator<Channel> mChannelComparator; 75 private long mSelectedChannelId; 76 private int mBackStackCount; 77 private boolean mShowViewScheduleOptionInDialog; 78 79 private String mFragmentTitle; 80 private String mProrityActionTitle; 81 private String mProrityActionHighestText; 82 private String mProrityActionLowestText; 83 private String mChannelsActionTitle; 84 private String mChannelsActionAllText; 85 private LongSparseArray<Channel> mId2Channel = new LongSparseArray<>(); 86 private List<Channel> mChannels = new ArrayList<>(); 87 private EpisodicProgramLoadTask mEpisodicProgramLoadTask; 88 89 private GuidedAction mPriorityGuidedAction; 90 private GuidedAction mChannelsGuidedAction; 91 92 @Override onAttach(Context context)93 public void onAttach(Context context) { 94 super.onAttach(context); 95 mBackStackCount = getFragmentManager().getBackStackEntryCount(); 96 mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); 97 mSeriesRecordingId = getArguments().getLong(DvrSeriesSettingsActivity.SERIES_RECORDING_ID); 98 mSeriesRecording = mDvrDataManager.getSeriesRecording(mSeriesRecordingId); 99 if (mSeriesRecording == null) { 100 getActivity().finish(); 101 return; 102 } 103 mDvrManager = TvApplication.getSingletons(context).getDvrManager(); 104 mShowViewScheduleOptionInDialog = getArguments().getBoolean( 105 DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG); 106 mDvrDataManager.addSeriesRecordingListener(this); 107 long[] channelIds = getArguments().getLongArray(DvrSeriesSettingsActivity.CHANNEL_ID_LIST); 108 mChannelDataManager = TvApplication.getSingletons(context).getChannelDataManager(); 109 if (channelIds == null) { 110 Channel channel = mChannelDataManager.getChannel(mSeriesRecording.getChannelId()); 111 if (channel != null) { 112 mId2Channel.put(channel.getId(), channel); 113 mChannels.add(channel); 114 } 115 collectChannelsInBackground(); 116 } else { 117 for (long channelId : channelIds) { 118 Channel channel = mChannelDataManager.getChannel(channelId); 119 if (channel != null) { 120 mId2Channel.put(channel.getId(), channel); 121 mChannels.add(channel); 122 } 123 } 124 } 125 mChannelOption = mSeriesRecording.getChannelOption(); 126 mSelectedChannelId = Channel.INVALID_ID; 127 if (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE) { 128 Channel channel = mChannelDataManager.getChannel(mSeriesRecording.getChannelId()); 129 if (channel != null) { 130 mSelectedChannelId = channel.getId(); 131 } else { 132 mChannelOption = SeriesRecording.OPTION_CHANNEL_ALL; 133 } 134 } 135 mChannelComparator = new Channel.DefaultComparator(context, 136 TvApplication.getSingletons(context).getTvInputManagerHelper()); 137 mChannels.sort(mChannelComparator); 138 mFragmentTitle = getString(R.string.dvr_series_settings_title); 139 mProrityActionTitle = getString(R.string.dvr_series_settings_priority); 140 mProrityActionHighestText = getString(R.string.dvr_series_settings_priority_highest); 141 mProrityActionLowestText = getString(R.string.dvr_series_settings_priority_lowest); 142 mChannelsActionTitle = getString(R.string.dvr_series_settings_channels); 143 mChannelsActionAllText = getString(R.string.dvr_series_settings_channels_all); 144 } 145 146 @Override onDetach()147 public void onDetach() { 148 super.onDetach(); 149 mDvrDataManager.removeSeriesRecordingListener(this); 150 if (mEpisodicProgramLoadTask != null) { 151 mEpisodicProgramLoadTask.cancel(true); 152 mEpisodicProgramLoadTask = null; 153 } 154 } 155 156 @Override onDestroy()157 public void onDestroy() { 158 DvrManager dvrManager = TvApplication.getSingletons(getActivity()).getDvrManager(); 159 if (getFragmentManager().getBackStackEntryCount() == mBackStackCount 160 && getArguments() 161 .getBoolean(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING) 162 && dvrManager.canRemoveSeriesRecording(mSeriesRecordingId)) { 163 dvrManager.removeSeriesRecording(mSeriesRecordingId); 164 } 165 super.onDestroy(); 166 } 167 168 @Override onCreateGuidance(Bundle savedInstanceState)169 public Guidance onCreateGuidance(Bundle savedInstanceState) { 170 String breadcrumb = mSeriesRecording.getTitle(); 171 String title = mFragmentTitle; 172 return new Guidance(title, null, breadcrumb, null); 173 } 174 175 @Override onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState)176 public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) { 177 mPriorityGuidedAction = new GuidedAction.Builder(getActivity()) 178 .id(ACTION_ID_PRIORITY) 179 .title(mProrityActionTitle) 180 .build(); 181 updatePriorityGuidedAction(false); 182 actions.add(mPriorityGuidedAction); 183 184 mChannelsGuidedAction = new GuidedAction.Builder(getActivity()) 185 .id(ACTION_ID_CHANNEL) 186 .title(mChannelsActionTitle) 187 .subActions(buildChannelSubAction()) 188 .build(); 189 actions.add(mChannelsGuidedAction); 190 updateChannelsGuidedAction(false); 191 } 192 193 @Override onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState)194 public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) { 195 actions.add(new GuidedAction.Builder(getActivity()) 196 .clickAction(GuidedAction.ACTION_ID_OK) 197 .build()); 198 actions.add(new GuidedAction.Builder(getActivity()) 199 .clickAction(GuidedAction.ACTION_ID_CANCEL) 200 .build()); 201 } 202 203 @Override onGuidedActionClicked(GuidedAction action)204 public void onGuidedActionClicked(GuidedAction action) { 205 long actionId = action.getId(); 206 if (actionId == GuidedAction.ACTION_ID_OK) { 207 if (mEpisodicProgramLoadTask != null) { 208 mEpisodicProgramLoadTask.cancel(true); 209 mEpisodicProgramLoadTask = null; 210 } 211 if (mChannelOption != mSeriesRecording.getChannelOption() 212 || mSeriesRecording.isStopped() 213 || (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE 214 && mSeriesRecording.getChannelId() != mSelectedChannelId)) { 215 SeriesRecording.Builder builder = SeriesRecording.buildFrom(mSeriesRecording) 216 .setChannelOption(mChannelOption) 217 .setState(SeriesRecording.STATE_SERIES_NORMAL); 218 if (mSelectedChannelId != Channel.INVALID_ID) { 219 builder.setChannelId(mSelectedChannelId); 220 } 221 TvApplication.getSingletons(getContext()).getDvrManager() 222 .updateSeriesRecording(builder.build()); 223 SeriesRecordingScheduler scheduler = 224 SeriesRecordingScheduler.getInstance(getContext()); 225 // Since dialog is used even after the fragment is closed, we should 226 // use application context. 227 ProgressDialog dialog = ProgressDialog.show(getContext(), null, getString( 228 R.string.dvr_series_schedules_progress_message_updating_programs)); 229 scheduler.addOnSeriesRecordingUpdatedListener( 230 new OnSeriesRecordingUpdatedListener() { 231 @Override 232 public void onSeriesRecordingUpdated(SeriesRecording... seriesRecordings) { 233 for (SeriesRecording seriesRecording : seriesRecordings) { 234 if (seriesRecording.getId() == mSeriesRecordingId) { 235 dialog.dismiss(); 236 scheduler.removeOnSeriesRecordingUpdatedListener(this); 237 showConfirmDialog(); 238 return; 239 } 240 } 241 } 242 }); 243 } else { 244 showConfirmDialog(); 245 } 246 } else if (actionId == GuidedAction.ACTION_ID_CANCEL) { 247 finishGuidedStepFragments(); 248 } else if (actionId == ACTION_ID_PRIORITY) { 249 FragmentManager fragmentManager = getFragmentManager(); 250 PrioritySettingsFragment fragment = new PrioritySettingsFragment(); 251 Bundle args = new Bundle(); 252 args.putLong(PrioritySettingsFragment.COME_FROM_SERIES_RECORDING_ID, 253 mSeriesRecording.getId()); 254 fragment.setArguments(args); 255 GuidedStepFragment.add(fragmentManager, fragment, R.id.dvr_settings_view_frame); 256 } 257 } 258 259 @Override onSubGuidedActionClicked(GuidedAction action)260 public boolean onSubGuidedActionClicked(GuidedAction action) { 261 long actionId = action.getId(); 262 if (actionId == SUB_ACTION_ID_CHANNEL_ALL) { 263 mChannelOption = SeriesRecording.OPTION_CHANNEL_ALL; 264 mSelectedChannelId = Channel.INVALID_ID; 265 updateChannelsGuidedAction(true); 266 return true; 267 } else if (actionId > SUB_ACTION_ID_CHANNEL_ONE_BASE) { 268 mChannelOption = SeriesRecording.OPTION_CHANNEL_ONE; 269 mSelectedChannelId = actionId - SUB_ACTION_ID_CHANNEL_ONE_BASE; 270 updateChannelsGuidedAction(true); 271 return true; 272 } 273 return false; 274 } 275 276 @Override onCreateButtonActionsStylist()277 public GuidedActionsStylist onCreateButtonActionsStylist() { 278 return new DvrGuidedActionsStylist(true); 279 } 280 updateChannelsGuidedAction(boolean notifyActionChanged)281 private void updateChannelsGuidedAction(boolean notifyActionChanged) { 282 if (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL) { 283 mChannelsGuidedAction.setDescription(mChannelsActionAllText); 284 } else { 285 mChannelsGuidedAction.setDescription(mId2Channel.get(mSelectedChannelId) 286 .getDisplayText()); 287 } 288 if (notifyActionChanged) { 289 notifyActionChanged(findActionPositionById(ACTION_ID_CHANNEL)); 290 } 291 } 292 updatePriorityGuidedAction(boolean notifyActionChanged)293 private void updatePriorityGuidedAction(boolean notifyActionChanged) { 294 int totalSeriesCount = 0; 295 int priorityOrder = 0; 296 for (SeriesRecording seriesRecording : mDvrDataManager.getSeriesRecordings()) { 297 if (seriesRecording.getState() == SeriesRecording.STATE_SERIES_NORMAL 298 || seriesRecording.getId() == mSeriesRecording.getId()) { 299 ++totalSeriesCount; 300 } 301 if (seriesRecording.getState() == SeriesRecording.STATE_SERIES_NORMAL 302 && seriesRecording.getId() != mSeriesRecording.getId() 303 && seriesRecording.getPriority() > mSeriesRecording.getPriority()) { 304 ++priorityOrder; 305 } 306 } 307 if (priorityOrder == 0) { 308 mPriorityGuidedAction.setDescription(mProrityActionHighestText); 309 } else if (priorityOrder >= totalSeriesCount - 1) { 310 mPriorityGuidedAction.setDescription(mProrityActionLowestText); 311 } else { 312 mPriorityGuidedAction.setDescription(getString( 313 R.string.dvr_series_settings_priority_rank, priorityOrder + 1)); 314 } 315 if (notifyActionChanged) { 316 notifyActionChanged(findActionPositionById(ACTION_ID_PRIORITY)); 317 } 318 } 319 collectChannelsInBackground()320 private void collectChannelsInBackground() { 321 if (mEpisodicProgramLoadTask != null) { 322 mEpisodicProgramLoadTask.cancel(true); 323 } 324 mEpisodicProgramLoadTask = new EpisodicProgramLoadTask(getContext(), mSeriesRecording) { 325 @Override 326 protected void onPostExecute(List<Program> programs) { 327 mEpisodicProgramLoadTask = null; 328 Set<Long> channelIds = new HashSet<>(); 329 for (Program program : programs) { 330 channelIds.add(program.getChannelId()); 331 } 332 boolean channelAdded = false; 333 for (Long channelId : channelIds) { 334 if (mId2Channel.get(channelId) != null) { 335 continue; 336 } 337 Channel channel = mChannelDataManager.getChannel(channelId); 338 if (channel != null) { 339 channelAdded = true; 340 mId2Channel.put(channelId, channel); 341 mChannels.add(channel); 342 if (DEBUG) Log.d(TAG, "Added channel: " + channel); 343 } 344 } 345 if (!channelAdded) { 346 return; 347 } 348 mChannels.sort(mChannelComparator); 349 mChannelsGuidedAction.setSubActions(buildChannelSubAction()); 350 notifyActionChanged(findActionPositionById(ACTION_ID_CHANNEL)); 351 if (DEBUG) Log.d(TAG, "Complete EpisodicProgramLoadTask"); 352 } 353 }.setLoadCurrentProgram(true) 354 .setLoadDisallowedProgram(true) 355 .setLoadScheduledEpisode(true) 356 .setIgnoreChannelOption(true); 357 mEpisodicProgramLoadTask.execute(); 358 } 359 buildChannelSubAction()360 private List<GuidedAction> buildChannelSubAction() { 361 List<GuidedAction> channelSubActions = new ArrayList<>(); 362 channelSubActions.add(new GuidedAction.Builder(getActivity()) 363 .id(SUB_ACTION_ID_CHANNEL_ALL) 364 .title(mChannelsActionAllText) 365 .build()); 366 for (Channel channel : mChannels) { 367 channelSubActions.add(new GuidedAction.Builder(getActivity()) 368 .id(SUB_ACTION_ID_CHANNEL_ONE_BASE + channel.getId()) 369 .title(channel.getDisplayText()) 370 .build()); 371 } 372 return channelSubActions; 373 } 374 showConfirmDialog()375 private void showConfirmDialog() { 376 DvrUiHelper.StartSeriesScheduledDialogActivity( 377 getContext(), mSeriesRecording, mShowViewScheduleOptionInDialog); 378 finishGuidedStepFragments(); 379 } 380 381 @Override onSeriesRecordingAdded(SeriesRecording... seriesRecordings)382 public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { } 383 384 @Override onSeriesRecordingRemoved(SeriesRecording... seriesRecordings)385 public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { } 386 387 @Override onSeriesRecordingChanged(SeriesRecording... seriesRecordings)388 public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { 389 for (SeriesRecording seriesRecording : seriesRecordings) { 390 if (seriesRecording.getId() == mSeriesRecordingId) { 391 mSeriesRecording = seriesRecording; 392 updatePriorityGuidedAction(true); 393 return; 394 } 395 } 396 } 397 } 398