/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tv.dvr.ui; import android.graphics.drawable.Drawable; import android.media.tv.TvInputInfo; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.leanback.widget.GuidanceStylist.Guidance; import androidx.leanback.widget.GuidedAction; import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.api.Channel; import com.android.tv.data.api.Program; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.recorder.ConflictChecker; import com.android.tv.dvr.recorder.ConflictChecker.OnUpcomingConflictChangeListener; import com.android.tv.util.Utils; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; public abstract class DvrConflictFragment extends DvrGuidedStepFragment { private static final String TAG = "DvrConflictFragment"; private static final boolean DEBUG = false; private static final int ACTION_DELETE_CONFLICT = 1; private static final int ACTION_CANCEL = 2; private static final int ACTION_VIEW_SCHEDULES = 3; // The program count which will be listed in the description. This is the number of the // program strings in R.plurals.dvr_program_conflict_dialog_description_many. private static final int LISTED_PROGRAM_COUNT = 2; protected List mConflicts; void setConflicts(List conflicts) { mConflicts = conflicts; } List getConflicts() { return mConflicts; } @Override public int onProvideTheme() { return R.style.Theme_TV_Dvr_Conflict_GuidedStep; } @Override public void onCreateActions(@NonNull List actions, Bundle savedInstanceState) { actions.add( new GuidedAction.Builder(getContext()) .clickAction(GuidedAction.ACTION_ID_OK) .build()); actions.add( new GuidedAction.Builder(getContext()) .id(ACTION_VIEW_SCHEDULES) .title(R.string.dvr_action_view_schedules) .build()); } @Override public void onTrackedGuidedActionClicked(GuidedAction action) { if (action.getId() == ACTION_VIEW_SCHEDULES) { DvrUiHelper.startSchedulesActivityForOneTimeRecordingConflict( getContext(), getConflicts()); } dismissDialog(); // Finish the Recording setting Activity on dismissal. if (getActivity() instanceof DvrRecordingSettingsActivity) { getActivity().finish(); } } @Override public String getTrackerLabelForGuidedAction(GuidedAction action) { long actionId = getId(); if (actionId == ACTION_VIEW_SCHEDULES) { return "view-schedules"; } else { return super.getTrackerLabelForGuidedAction(action); } } String getConflictDescription() { List titles = new ArrayList<>(); HashSet titleSet = new HashSet<>(); for (ScheduledRecording schedule : getConflicts()) { String scheduleTitle = getScheduleTitle(schedule); if (scheduleTitle != null && !titleSet.contains(scheduleTitle)) { titles.add(scheduleTitle); titleSet.add(scheduleTitle); } } switch (titles.size()) { case 0: Log.i( TAG, "Conflict has been resolved by any reason. Maybe input might have" + " been deleted."); return null; case 1: return getResources() .getString( R.string.dvr_program_conflict_dialog_description_1, titles.get(0)); case 2: return getResources() .getString( R.string.dvr_program_conflict_dialog_description_2, titles.get(0), titles.get(1)); case 3: return getResources() .getString( R.string.dvr_program_conflict_dialog_description_3, titles.get(0), titles.get(1)); default: return getResources() .getQuantityString( R.plurals.dvr_program_conflict_dialog_description_many, titles.size() - LISTED_PROGRAM_COUNT, titles.get(0), titles.get(1), titles.size() - LISTED_PROGRAM_COUNT); } } @Nullable private String getScheduleTitle(ScheduledRecording schedule) { if (schedule.getType() == ScheduledRecording.TYPE_TIMED) { Channel channel = TvSingletons.getSingletons(getContext()) .getChannelDataManager() .getChannel(schedule.getChannelId()); if (channel != null) { return channel.getDisplayName(); } else { return null; } } else { return schedule.getProgramTitle(); } } /** A fragment to show the program conflict. */ public static class DvrProgramConflictFragment extends DvrConflictFragment { private Program mProgram; @Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Bundle args = getArguments(); if (args != null) { mProgram = args.getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM); } SoftPreconditions.checkArgument(mProgram != null); TvInputInfo input = Utils.getTvInputInfoForProgram(getContext(), mProgram); SoftPreconditions.checkNotNull(input); List conflicts = null; if (input != null) { conflicts = TvSingletons.getSingletons(getContext()) .getDvrManager() .getConflictingSchedules(mProgram); } if (conflicts == null) { conflicts = Collections.emptyList(); } if (conflicts.isEmpty()) { dismissDialog(); } setConflicts(conflicts); return super.onCreateView(inflater, container, savedInstanceState); } @NonNull @Override public Guidance onCreateGuidance(Bundle savedInstanceState) { String title = getResources().getString(R.string.dvr_program_conflict_dialog_title); String descriptionPrefix = getString( R.string.dvr_program_conflict_dialog_description_prefix, mProgram.getTitle()); String description = getConflictDescription(); if (description == null) { dismissDialog(); } Drawable icon = getResources().getDrawable(R.drawable.quantum_ic_error_white_48, null); return new Guidance(title, descriptionPrefix + " " + description, null, icon); } @Override public String getTrackerPrefix() { return "DvrProgramConflictFragment"; } } /** A fragment to show the channel recording conflict. */ public static class DvrChannelRecordConflictFragment extends DvrConflictFragment { private Channel mChannel; private long mStartTimeMs; private long mEndTimeMs; @Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Bundle args = getArguments(); long channelId = args.getLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID); mChannel = TvSingletons.getSingletons(getContext()) .getChannelDataManager() .getChannel(channelId); SoftPreconditions.checkArgument(mChannel != null); TvInputInfo input = Utils.getTvInputInfoForChannelId(getContext(), mChannel.getId()); SoftPreconditions.checkNotNull(input); List conflicts = null; if (input != null) { mStartTimeMs = args.getLong(DvrHalfSizedDialogFragment.KEY_START_TIME_MS); mEndTimeMs = args.getLong(DvrHalfSizedDialogFragment.KEY_END_TIME_MS); conflicts = TvSingletons.getSingletons(getContext()) .getDvrManager() .getConflictingSchedules( mChannel.getId(), mStartTimeMs, mEndTimeMs); } if (conflicts == null) { conflicts = Collections.emptyList(); } if (conflicts.isEmpty()) { dismissDialog(); } setConflicts(conflicts); return super.onCreateView(inflater, container, savedInstanceState); } @NonNull @Override public Guidance onCreateGuidance(Bundle savedInstanceState) { String title = getResources().getString(R.string.dvr_channel_conflict_dialog_title); String descriptionPrefix = getString( R.string.dvr_channel_conflict_dialog_description_prefix, mChannel.getDisplayName()); String description = getConflictDescription(); if (description == null) { dismissDialog(); } Drawable icon = getResources().getDrawable(R.drawable.quantum_ic_error_white_48, null); return new Guidance(title, descriptionPrefix + " " + description, null, icon); } @Override public String getTrackerPrefix() { return "DvrChannelRecordConflictFragment"; } } /** * A fragment to show the channel watching conflict. * *

This fragment is automatically closed when there are no upcoming conflicts. */ public static class DvrChannelWatchConflictFragment extends DvrConflictFragment implements OnUpcomingConflictChangeListener { private long mChannelId; @Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Bundle args = getArguments(); if (args != null) { mChannelId = args.getLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID); } SoftPreconditions.checkArgument(mChannelId != Channel.INVALID_ID); ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker(); List conflicts = null; if (checker != null) { checker.addOnUpcomingConflictChangeListener(this); conflicts = checker.getUpcomingConflicts(); if (DEBUG) Log.d(TAG, "onCreateView: upcoming conflicts: " + conflicts); if (conflicts.isEmpty()) { dismissDialog(); } } if (conflicts == null) { if (DEBUG) Log.d(TAG, "onCreateView: There's no conflict."); conflicts = Collections.emptyList(); } if (conflicts.isEmpty()) { dismissDialog(); } setConflicts(conflicts); return super.onCreateView(inflater, container, savedInstanceState); } @NonNull @Override public Guidance onCreateGuidance(Bundle savedInstanceState) { String title = getResources().getString(R.string.dvr_epg_channel_watch_conflict_dialog_title); String description = getResources() .getString(R.string.dvr_epg_channel_watch_conflict_dialog_description); return new Guidance(title, description, null, null); } @Override public void onCreateActions( @NonNull List actions, Bundle savedInstanceState) { actions.add( new GuidedAction.Builder(getContext()) .id(ACTION_DELETE_CONFLICT) .title(R.string.dvr_action_delete_schedule) .build()); actions.add( new GuidedAction.Builder(getContext()) .id(ACTION_CANCEL) .title(R.string.dvr_action_record_program) .build()); } @Override public void onTrackedGuidedActionClicked(GuidedAction action) { if (action.getId() == ACTION_CANCEL) { ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker(); if (checker != null) { checker.setCheckedConflictsForChannel(mChannelId, getConflicts()); } } else if (action.getId() == ACTION_DELETE_CONFLICT) { for (ScheduledRecording schedule : mConflicts) { if (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { getDvrManager().stopRecording(schedule); } else { getDvrManager().removeScheduledRecording(schedule); } } } super.onGuidedActionClicked(action); } @Override public String getTrackerPrefix() { return "DvrChannelWatchConflictFragment"; } @Override public String getTrackerLabelForGuidedAction(GuidedAction action) { long actionId = action.getId(); if (actionId == ACTION_CANCEL) { return "cancel"; } else if (actionId == ACTION_DELETE_CONFLICT) { return "delete"; } else { return super.getTrackerLabelForGuidedAction(action); } } @Override public void onDetach() { ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker(); if (checker != null) { checker.removeOnUpcomingConflictChangeListener(this); } super.onDetach(); } @Override public void onUpcomingConflictChange() { ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker(); if (checker == null || checker.getUpcomingConflicts().isEmpty()) { if (DEBUG) Log.d(TAG, "onUpcomingConflictChange: There's no conflict."); dismissDialog(); } } } }