165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko/* 265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Copyright (C) 2016 The Android Open Source Project 365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License"); 565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * you may not use this file except in compliance with the License. 665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * You may obtain a copy of the License at 765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * http://www.apache.org/licenses/LICENSE-2.0 965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 1065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Unless required by applicable law or agreed to in writing, software 1165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS, 1265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * See the License for the specific language governing permissions and 1465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * limitations under the License 1565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 1665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 1765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopackage com.android.tv.dvr; 1865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 1965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.Context; 2065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.SharedPreferences; 2165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.media.tv.TvInputManager; 22d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport android.support.annotation.IntDef; 23944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport com.android.tv.common.util.SharedPreferencesUtils; 24633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport com.android.tv.dvr.data.RecordedProgram; 25d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.lang.annotation.Retention; 26d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.lang.annotation.RetentionPolicy; 2765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.ArrayList; 2865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.HashMap; 2965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Map; 3065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Set; 3165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.concurrent.CopyOnWriteArraySet; 3265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 3365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko/** 3495961816a768da387f0b5523cf4363ace2044089Nick Chalko * A class to manage DVR watched state. It will remember and provides previous watched position of 3595961816a768da387f0b5523cf4363ace2044089Nick Chalko * DVR playback. 3665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 3765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopublic class DvrWatchedPositionManager { 3865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private SharedPreferences mWatchedPositions; 3965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Map<Long, Set> mListeners = new HashMap<>(); 4065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 41d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko /** 42d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * The minimum percentage of recorded program being watched that will be considered as being 43d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * completely watched. 44d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */ 45d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public static final float DVR_WATCHED_THRESHOLD_RATE = 0.98f; 46d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 47d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko @Retention(RetentionPolicy.SOURCE) 48d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko @IntDef({DVR_WATCHED_STATUS_NEW, DVR_WATCHED_STATUS_WATCHING, DVR_WATCHED_STATUS_WATCHED}) 49d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public @interface DvrWatchedStatus {} 5095961816a768da387f0b5523cf4363ace2044089Nick Chalko /** The status indicates the recorded program has not been watched at all. */ 51d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public static final int DVR_WATCHED_STATUS_NEW = 0; 5295961816a768da387f0b5523cf4363ace2044089Nick Chalko /** The status indicates the recorded program is being watched. */ 53d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public static final int DVR_WATCHED_STATUS_WATCHING = 1; 5495961816a768da387f0b5523cf4363ace2044089Nick Chalko /** The status indicates the recorded program was completely watched. */ 55d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public static final int DVR_WATCHED_STATUS_WATCHED = 2; 56d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 5765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public DvrWatchedPositionManager(Context context) { 5895961816a768da387f0b5523cf4363ace2044089Nick Chalko mWatchedPositions = 5995961816a768da387f0b5523cf4363ace2044089Nick Chalko context.getSharedPreferences( 6095961816a768da387f0b5523cf4363ace2044089Nick Chalko SharedPreferencesUtils.SHARED_PREF_DVR_WATCHED_POSITION, 6195961816a768da387f0b5523cf4363ace2044089Nick Chalko Context.MODE_PRIVATE); 6265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 6365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 6495961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Sets the watched position of the give program. */ 6565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void setWatchedPosition(long recordedProgramId, long positionMs) { 6665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mWatchedPositions.edit().putLong(Long.toString(recordedProgramId), positionMs).apply(); 6765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko notifyWatchedPositionChanged(recordedProgramId, positionMs); 6865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 6965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 7095961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Gets the watched position of the give program. */ 7165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public long getWatchedPosition(long recordedProgramId) { 7295961816a768da387f0b5523cf4363ace2044089Nick Chalko return mWatchedPositions.getLong( 7395961816a768da387f0b5523cf4363ace2044089Nick Chalko Long.toString(recordedProgramId), TvInputManager.TIME_SHIFT_INVALID_TIME); 7465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 7565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 7695961816a768da387f0b5523cf4363ace2044089Nick Chalko @DvrWatchedStatus 7795961816a768da387f0b5523cf4363ace2044089Nick Chalko public int getWatchedStatus(RecordedProgram recordedProgram) { 78d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko long watchedPosition = getWatchedPosition(recordedProgram.getId()); 79d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (watchedPosition == TvInputManager.TIME_SHIFT_INVALID_TIME) { 80d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return DVR_WATCHED_STATUS_NEW; 8195961816a768da387f0b5523cf4363ace2044089Nick Chalko } else if (watchedPosition 8295961816a768da387f0b5523cf4363ace2044089Nick Chalko > recordedProgram.getDurationMillis() * DVR_WATCHED_THRESHOLD_RATE) { 83d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return DVR_WATCHED_STATUS_WATCHED; 84d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } else { 85d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return DVR_WATCHED_STATUS_WATCHING; 86d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 87d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 88d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 8995961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Adds {@link WatchedPositionChangedListener}. */ 9065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void addListener(WatchedPositionChangedListener listener, long recordedProgramId) { 9165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (recordedProgramId == RecordedProgram.ID_NOT_SET) { 9265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 9365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 9465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Set<WatchedPositionChangedListener> listenerSet = mListeners.get(recordedProgramId); 9565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (listenerSet == null) { 9665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko listenerSet = new CopyOnWriteArraySet<>(); 9765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mListeners.put(recordedProgramId, listenerSet); 9865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 9965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko listenerSet.add(listener); 10065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 10165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 10295961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Removes {@link WatchedPositionChangedListener}. */ 10365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void removeListener(WatchedPositionChangedListener listener) { 10465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (long recordedProgramId : new ArrayList<>(mListeners.keySet())) { 10565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko removeListener(listener, recordedProgramId); 10665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 10765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 10865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 10995961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Removes {@link WatchedPositionChangedListener}. */ 11065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void removeListener(WatchedPositionChangedListener listener, long recordedProgramId) { 11165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Set<WatchedPositionChangedListener> listenerSet = mListeners.get(recordedProgramId); 11265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (listenerSet == null) { 11365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 11465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 11565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko listenerSet.remove(listener); 11665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (listenerSet.isEmpty()) { 11765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mListeners.remove(recordedProgramId); 11865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 11965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 12065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 12165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void notifyWatchedPositionChanged(long recordedProgramId, long positionMs) { 12265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Set<WatchedPositionChangedListener> listenerSet = mListeners.get(recordedProgramId); 12365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (listenerSet == null) { 12465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 12565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 12665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (WatchedPositionChangedListener listener : listenerSet) { 12765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko listener.onWatchedPositionChanged(recordedProgramId, positionMs); 12865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 12965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 13065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 13165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public interface WatchedPositionChangedListener { 13295961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Called when the watched position of some program is changed. */ 13365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko void onWatchedPositionChanged(long recordedProgramId, long positionMs); 13465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 13565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko} 136