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.content.Context; 20 import android.media.tv.TvInputManager; 21 import android.os.Bundle; 22 import android.support.v17.leanback.app.GuidedStepFragment; 23 import android.support.v17.leanback.widget.GuidanceStylist.Guidance; 24 import android.support.v17.leanback.widget.GuidedAction; 25 import android.support.v17.leanback.widget.GuidedActionsStylist; 26 import android.text.TextUtils; 27 import android.view.ViewGroup.LayoutParams; 28 import android.widget.Toast; 29 30 import com.android.tv.R; 31 import com.android.tv.TvApplication; 32 import com.android.tv.common.SoftPreconditions; 33 import com.android.tv.dvr.DvrDataManager; 34 import com.android.tv.dvr.DvrManager; 35 import com.android.tv.dvr.DvrWatchedPositionManager; 36 import com.android.tv.dvr.RecordedProgram; 37 import com.android.tv.dvr.SeriesRecording; 38 import com.android.tv.ui.GuidedActionsStylistWithDivider; 39 40 import java.util.ArrayList; 41 import java.util.Collections; 42 import java.util.HashSet; 43 import java.util.List; 44 import java.util.Set; 45 import java.util.concurrent.TimeUnit; 46 47 /** 48 * Fragment for DVR series recording settings. 49 */ 50 public class SeriesDeletionFragment extends GuidedStepFragment { 51 private static final long WATCHED_TIME_UNIT_THRESHOLD = TimeUnit.MINUTES.toMillis(2); 52 53 // Since recordings' IDs are used as its check actions' IDs, which are random positive numbers, 54 // negative values are used by other actions to prevent duplicated IDs. 55 private static final long ACTION_ID_SELECT_WATCHED = -110; 56 private static final long ACTION_ID_SELECT_ALL = -111; 57 private static final long ACTION_ID_DELETE = -112; 58 59 private DvrDataManager mDvrDataManager; 60 private DvrWatchedPositionManager mDvrWatchedPositionManager; 61 private List<RecordedProgram> mRecordings; 62 private final Set<Long> mWatchedRecordings = new HashSet<>(); 63 private boolean mAllSelected; 64 private long mSeriesRecordingId; 65 private int mOneLineActionHeight; 66 67 @Override onAttach(Context context)68 public void onAttach(Context context) { 69 super.onAttach(context); 70 mSeriesRecordingId = getArguments() 71 .getLong(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, -1); 72 SoftPreconditions.checkArgument(mSeriesRecordingId != -1); 73 mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); 74 mDvrWatchedPositionManager = 75 TvApplication.getSingletons(context).getDvrWatchedPositionManager(); 76 mRecordings = mDvrDataManager.getRecordedPrograms(mSeriesRecordingId); 77 mOneLineActionHeight = getResources().getDimensionPixelSize( 78 R.dimen.dvr_settings_one_line_action_container_height); 79 if (mRecordings.isEmpty()) { 80 Toast.makeText(getActivity(), getString(R.string.dvr_series_deletion_no_recordings), 81 Toast.LENGTH_LONG).show(); 82 finishGuidedStepFragments(); 83 return; 84 } 85 Collections.sort(mRecordings, RecordedProgram.EPISODE_COMPARATOR); 86 } 87 88 @Override onCreateGuidance(Bundle savedInstanceState)89 public Guidance onCreateGuidance(Bundle savedInstanceState) { 90 String breadcrumb = null; 91 SeriesRecording series = mDvrDataManager.getSeriesRecording(mSeriesRecordingId); 92 if (series != null) { 93 breadcrumb = series.getTitle(); 94 } 95 return new Guidance(getString(R.string.dvr_series_deletion_title), 96 getString(R.string.dvr_series_deletion_description), breadcrumb, null); 97 } 98 99 @Override onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState)100 public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) { 101 actions.add(new GuidedAction.Builder(getActivity()) 102 .id(ACTION_ID_SELECT_WATCHED) 103 .title(getString(R.string.dvr_series_select_watched)) 104 .build()); 105 actions.add(new GuidedAction.Builder(getActivity()) 106 .id(ACTION_ID_SELECT_ALL) 107 .title(getString(R.string.dvr_series_select_all)) 108 .build()); 109 actions.add(GuidedActionsStylistWithDivider.createDividerAction(getContext())); 110 for (RecordedProgram recording : mRecordings) { 111 long watchedPositionMs = 112 mDvrWatchedPositionManager.getWatchedPosition(recording.getId()); 113 String title = recording.getEpisodeDisplayTitle(getContext()); 114 if (TextUtils.isEmpty(title)) { 115 title = TextUtils.isEmpty(recording.getTitle()) ? 116 getString(R.string.channel_banner_no_title) : recording.getTitle(); 117 } 118 String description; 119 if (watchedPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { 120 description = getWatchedString(watchedPositionMs, recording.getDurationMillis()); 121 mWatchedRecordings.add(recording.getId()); 122 } else { 123 description = getString(R.string.dvr_series_never_watched); 124 } 125 actions.add(new GuidedAction.Builder(getActivity()) 126 .id(recording.getId()) 127 .title(title) 128 .description(description) 129 .checkSetId(GuidedAction.CHECKBOX_CHECK_SET_ID) 130 .build()); 131 } 132 } 133 134 @Override onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState)135 public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) { 136 actions.add(new GuidedAction.Builder(getActivity()) 137 .id(ACTION_ID_DELETE) 138 .title(getString(R.string.dvr_detail_delete)) 139 .build()); 140 actions.add(new GuidedAction.Builder(getActivity()) 141 .clickAction(GuidedAction.ACTION_ID_CANCEL) 142 .build()); 143 } 144 145 @Override onGuidedActionClicked(GuidedAction action)146 public void onGuidedActionClicked(GuidedAction action) { 147 long actionId = action.getId(); 148 if (actionId == ACTION_ID_DELETE) { 149 List<Long> idsToDelete = new ArrayList<>(); 150 for (GuidedAction guidedAction : getActions()) { 151 if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID 152 && guidedAction.isChecked()) { 153 idsToDelete.add(guidedAction.getId()); 154 } 155 } 156 if (!idsToDelete.isEmpty()) { 157 DvrManager dvrManager = TvApplication.getSingletons(getActivity()).getDvrManager(); 158 dvrManager.removeRecordedPrograms(idsToDelete); 159 } 160 Toast.makeText(getContext(), getResources().getQuantityString( 161 R.plurals.dvr_msg_episodes_deleted, idsToDelete.size(), idsToDelete.size(), 162 mRecordings.size()), Toast.LENGTH_LONG).show(); 163 finishGuidedStepFragments(); 164 } else if (actionId == GuidedAction.ACTION_ID_CANCEL) { 165 finishGuidedStepFragments(); 166 } else if (actionId == ACTION_ID_SELECT_WATCHED) { 167 for (GuidedAction guidedAction : getActions()) { 168 if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID) { 169 long recordingId = guidedAction.getId(); 170 if (mWatchedRecordings.contains(recordingId)) { 171 guidedAction.setChecked(true); 172 } else { 173 guidedAction.setChecked(false); 174 } 175 notifyActionChanged(findActionPositionById(recordingId)); 176 } 177 } 178 mAllSelected = updateSelectAllState(); 179 } else if (actionId == ACTION_ID_SELECT_ALL) { 180 mAllSelected = !mAllSelected; 181 for (GuidedAction guidedAction : getActions()) { 182 if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID) { 183 guidedAction.setChecked(mAllSelected); 184 notifyActionChanged(findActionPositionById(guidedAction.getId())); 185 } 186 } 187 updateSelectAllState(action, mAllSelected); 188 } else { 189 mAllSelected = updateSelectAllState(); 190 } 191 } 192 193 @Override onCreateButtonActionsStylist()194 public GuidedActionsStylist onCreateButtonActionsStylist() { 195 return new DvrGuidedActionsStylist(true); 196 } 197 198 @Override onCreateActionsStylist()199 public GuidedActionsStylist onCreateActionsStylist() { 200 return new GuidedActionsStylistWithDivider() { 201 @Override 202 public void onBindViewHolder(ViewHolder vh, GuidedAction action) { 203 super.onBindViewHolder(vh, action); 204 if (action.getId() == ACTION_DIVIDER) { 205 return; 206 } 207 LayoutParams lp = vh.itemView.getLayoutParams(); 208 if (action.getCheckSetId() != GuidedAction.CHECKBOX_CHECK_SET_ID) { 209 lp.height = mOneLineActionHeight; 210 } else { 211 vh.itemView.setLayoutParams( 212 new LayoutParams(lp.width, LayoutParams.WRAP_CONTENT)); 213 } 214 } 215 }; 216 } 217 218 private String getWatchedString(long watchedPositionMs, long durationMs) { 219 if (durationMs > WATCHED_TIME_UNIT_THRESHOLD) { 220 return getResources().getString(R.string.dvr_series_watched_info_minutes, 221 Math.max(1, TimeUnit.MILLISECONDS.toMinutes(watchedPositionMs)), 222 TimeUnit.MILLISECONDS.toMinutes(durationMs)); 223 } else { 224 return getResources().getString(R.string.dvr_series_watched_info_seconds, 225 Math.max(1, TimeUnit.MILLISECONDS.toSeconds(watchedPositionMs)), 226 TimeUnit.MILLISECONDS.toSeconds(durationMs)); 227 } 228 } 229 230 private boolean updateSelectAllState() { 231 for (GuidedAction guidedAction : getActions()) { 232 if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID) { 233 if (!guidedAction.isChecked()) { 234 if (mAllSelected) { 235 updateSelectAllState(findActionById(ACTION_ID_SELECT_ALL), false); 236 } 237 return false; 238 } 239 } 240 } 241 if (!mAllSelected) { 242 updateSelectAllState(findActionById(ACTION_ID_SELECT_ALL), true); 243 } 244 return true; 245 } 246 247 private void updateSelectAllState(GuidedAction selectAll, boolean select) { 248 selectAll.setTitle(select ? getString(R.string.dvr_series_deselect_all) 249 : getString(R.string.dvr_series_select_all)); 250 notifyActionChanged(findActionPositionById(ACTION_ID_SELECT_ALL)); 251 } 252 } 253