/* * 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.content.Context; import android.media.tv.TvInputManager; import android.os.Bundle; import android.support.v17.leanback.app.GuidedStepFragment; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; import android.support.v17.leanback.widget.GuidedActionsStylist; import android.text.TextUtils; import android.view.ViewGroup.LayoutParams; import android.widget.Toast; import com.android.tv.R; import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.util.PermissionUtils; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.DvrWatchedPositionManager; import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.ui.GuidedActionsStylistWithDivider; import com.android.tv.util.Utils; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; /** Fragment for DVR series recording settings. */ public class DvrSeriesDeletionFragment extends GuidedStepFragment { private static final long WATCHED_TIME_UNIT_THRESHOLD = TimeUnit.MINUTES.toMillis(2); // Since recordings' IDs are used as its check actions' IDs, which are random positive numbers, // negative values are used by other actions to prevent duplicated IDs. private static final long ACTION_ID_SELECT_WATCHED = -110; private static final long ACTION_ID_SELECT_ALL = -111; private static final long ACTION_ID_DELETE = -112; private DvrManager mDvrManager; private DvrDataManager mDvrDataManager; private DvrWatchedPositionManager mDvrWatchedPositionManager; private List mRecordings; private final Set mWatchedRecordings = new HashSet<>(); private final List mIdsToDelete = new ArrayList<>(); private boolean mAllSelected; private long mSeriesRecordingId; private int mOneLineActionHeight; @Override public void onAttach(Context context) { super.onAttach(context); mSeriesRecordingId = getArguments().getLong(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, -1); SoftPreconditions.checkArgument(mSeriesRecordingId != -1); TvSingletons singletons = TvSingletons.getSingletons(context); mDvrManager = singletons.getDvrManager(); mDvrDataManager = singletons.getDvrDataManager(); mDvrWatchedPositionManager = singletons.getDvrWatchedPositionManager(); mRecordings = mDvrDataManager.getRecordedPrograms(mSeriesRecordingId); mOneLineActionHeight = getResources() .getDimensionPixelSize( R.dimen.dvr_settings_one_line_action_container_height); if (mRecordings.isEmpty()) { Toast.makeText( getActivity(), getString(R.string.dvr_series_deletion_no_recordings), Toast.LENGTH_LONG) .show(); finishGuidedStepFragments(); return; } Collections.sort(mRecordings, RecordedProgram.EPISODE_COMPARATOR); } @Override public Guidance onCreateGuidance(Bundle savedInstanceState) { String breadcrumb = null; SeriesRecording series = mDvrDataManager.getSeriesRecording(mSeriesRecordingId); if (series != null) { breadcrumb = series.getTitle(); } return new Guidance( getString(R.string.dvr_series_deletion_title), getString(R.string.dvr_series_deletion_description), breadcrumb, null); } @Override public void onCreateActions(List actions, Bundle savedInstanceState) { actions.add( new GuidedAction.Builder(getActivity()) .id(ACTION_ID_SELECT_WATCHED) .title(getString(R.string.dvr_series_select_watched)) .build()); actions.add( new GuidedAction.Builder(getActivity()) .id(ACTION_ID_SELECT_ALL) .title(getString(R.string.dvr_series_select_all)) .build()); actions.add(GuidedActionsStylistWithDivider.createDividerAction(getContext())); for (RecordedProgram recording : mRecordings) { long watchedPositionMs = mDvrWatchedPositionManager.getWatchedPosition(recording.getId()); String title = recording.getEpisodeDisplayTitle(getContext()); if (TextUtils.isEmpty(title)) { title = TextUtils.isEmpty(recording.getTitle()) ? getString(R.string.channel_banner_no_title) : recording.getTitle(); } String description; if (watchedPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { description = getWatchedString(watchedPositionMs, recording.getDurationMillis()); mWatchedRecordings.add(recording.getId()); } else { description = getString(R.string.dvr_series_never_watched); } actions.add( new GuidedAction.Builder(getActivity()) .id(recording.getId()) .title(title) .description(description) .checkSetId(GuidedAction.CHECKBOX_CHECK_SET_ID) .build()); } } @Override public void onCreateButtonActions(List actions, Bundle savedInstanceState) { actions.add( new GuidedAction.Builder(getActivity()) .id(ACTION_ID_DELETE) .title(getString(R.string.dvr_detail_delete)) .build()); actions.add( new GuidedAction.Builder(getActivity()) .clickAction(GuidedAction.ACTION_ID_CANCEL) .build()); } @Override public void onGuidedActionClicked(GuidedAction action) { long actionId = action.getId(); if (actionId == ACTION_ID_DELETE) { delete(); } else if (actionId == GuidedAction.ACTION_ID_CANCEL) { finishGuidedStepFragments(); } else if (actionId == ACTION_ID_SELECT_WATCHED) { for (GuidedAction guidedAction : getActions()) { if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID) { long recordingId = guidedAction.getId(); if (mWatchedRecordings.contains(recordingId)) { guidedAction.setChecked(true); } else { guidedAction.setChecked(false); } notifyActionChanged(findActionPositionById(recordingId)); } } mAllSelected = updateSelectAllState(); } else if (actionId == ACTION_ID_SELECT_ALL) { mAllSelected = !mAllSelected; for (GuidedAction guidedAction : getActions()) { if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID) { guidedAction.setChecked(mAllSelected); notifyActionChanged(findActionPositionById(guidedAction.getId())); } } updateSelectAllState(action, mAllSelected); } else { mAllSelected = updateSelectAllState(); } } @Override public GuidedActionsStylist onCreateButtonActionsStylist() { return new DvrGuidedActionsStylist(true); } @Override public GuidedActionsStylist onCreateActionsStylist() { return new GuidedActionsStylistWithDivider() { @Override public void onBindViewHolder(ViewHolder vh, GuidedAction action) { super.onBindViewHolder(vh, action); if (action.getId() == ACTION_DIVIDER) { return; } LayoutParams lp = vh.itemView.getLayoutParams(); if (action.getCheckSetId() != GuidedAction.CHECKBOX_CHECK_SET_ID) { lp.height = mOneLineActionHeight; } else { vh.itemView.setLayoutParams( new LayoutParams(lp.width, LayoutParams.WRAP_CONTENT)); } } }; } private void delete() { mIdsToDelete.clear(); for (GuidedAction guidedAction : getActions()) { if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID && guidedAction.isChecked()) { mIdsToDelete.add(guidedAction.getId()); } } ((DvrSeriesDeletionActivity) getActivity()).setIdsToDelete(mIdsToDelete); if (!PermissionUtils.hasWriteExternalStorage(getContext()) && doesAnySelectedRecordedProgramNeedWritePermission()) { DvrUiHelper.showWriteStoragePermissionRationaleDialog(getActivity()); } else { deleteSelectedIds(); } } private boolean doesAnySelectedRecordedProgramNeedWritePermission() { for (RecordedProgram r : mRecordings) { if (mIdsToDelete.contains(r.getId()) && DvrManager.isFile(r.getDataUri()) && !DvrManager.isFromBundledInput(r)) { return true; } } return false; } private void deleteSelectedIds() { if (!mIdsToDelete.isEmpty()) { mDvrManager.removeRecordedPrograms(mIdsToDelete, true); } Toast.makeText( getContext(), getResources() .getQuantityString( R.plurals.dvr_msg_episodes_deleted, mIdsToDelete.size(), mIdsToDelete.size(), mRecordings.size()), Toast.LENGTH_LONG) .show(); finishGuidedStepFragments(); } private String getWatchedString(long watchedPositionMs, long durationMs) { if (durationMs > WATCHED_TIME_UNIT_THRESHOLD) { return getResources() .getString( R.string.dvr_series_watched_info_minutes, Math.max(1, Utils.getRoundOffMinsFromMs(watchedPositionMs)), Utils.getRoundOffMinsFromMs(durationMs)); } else { return getResources() .getString( R.string.dvr_series_watched_info_seconds, Math.max(1, TimeUnit.MILLISECONDS.toSeconds(watchedPositionMs)), TimeUnit.MILLISECONDS.toSeconds(durationMs)); } } private boolean updateSelectAllState() { for (GuidedAction guidedAction : getActions()) { if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID) { if (!guidedAction.isChecked()) { if (mAllSelected) { updateSelectAllState(findActionById(ACTION_ID_SELECT_ALL), false); } return false; } } } if (!mAllSelected) { updateSelectAllState(findActionById(ACTION_ID_SELECT_ALL), true); } return true; } private void updateSelectAllState(GuidedAction selectAll, boolean select) { selectAll.setTitle( select ? getString(R.string.dvr_series_deselect_all) : getString(R.string.dvr_series_select_all)); notifyActionChanged(findActionPositionById(ACTION_ID_SELECT_ALL)); } }