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;
18 
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.media.tv.TvInputManager;
22 import android.support.annotation.IntDef;
23 
24 import com.android.tv.common.SharedPreferencesUtils;
25 
26 import java.lang.annotation.Retention;
27 import java.lang.annotation.RetentionPolicy;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.concurrent.CopyOnWriteArraySet;
33 
34 /**
35  * A class to manage DVR watched state.
36  * It will remember and provides previous watched position of DVR playback.
37  */
38 public class DvrWatchedPositionManager {
39     private final static String TAG = "DvrWatchedPositionManager";
40     private final boolean DEBUG = false;
41 
42     private SharedPreferences mWatchedPositions;
43     private final Map<Long, Set> mListeners = new HashMap<>();
44 
45     /**
46      * The minimum percentage of recorded program being watched that will be considered as being
47      * completely watched.
48      */
49     public static final float DVR_WATCHED_THRESHOLD_RATE = 0.98f;
50 
51     @Retention(RetentionPolicy.SOURCE)
52     @IntDef({DVR_WATCHED_STATUS_NEW, DVR_WATCHED_STATUS_WATCHING, DVR_WATCHED_STATUS_WATCHED})
53     public @interface DvrWatchedStatus {}
54     /**
55      * The status indicates the recorded program has not been watched at all.
56      */
57     public static final int DVR_WATCHED_STATUS_NEW = 0;
58     /**
59      * The status indicates the recorded program is being watched.
60      */
61     public static final int DVR_WATCHED_STATUS_WATCHING = 1;
62     /**
63      * The status indicates the recorded program was completely watched.
64      */
65     public static final int DVR_WATCHED_STATUS_WATCHED = 2;
66 
DvrWatchedPositionManager(Context context)67     public DvrWatchedPositionManager(Context context) {
68         mWatchedPositions = context.getSharedPreferences(
69                 SharedPreferencesUtils.SHARED_PREF_DVR_WATCHED_POSITION, Context.MODE_PRIVATE);
70     }
71 
72     /**
73      * Sets the watched position of the give program.
74      */
setWatchedPosition(long recordedProgramId, long positionMs)75     public void setWatchedPosition(long recordedProgramId, long positionMs) {
76         mWatchedPositions.edit().putLong(Long.toString(recordedProgramId), positionMs).apply();
77         notifyWatchedPositionChanged(recordedProgramId, positionMs);
78     }
79 
80     /**
81      * Gets the watched position of the give program.
82      */
getWatchedPosition(long recordedProgramId)83     public long getWatchedPosition(long recordedProgramId) {
84         return mWatchedPositions.getLong(Long.toString(recordedProgramId),
85                 TvInputManager.TIME_SHIFT_INVALID_TIME);
86     }
87 
getWatchedStatus(RecordedProgram recordedProgram)88     @DvrWatchedStatus public int getWatchedStatus(RecordedProgram recordedProgram) {
89         long watchedPosition = getWatchedPosition(recordedProgram.getId());
90         if (watchedPosition == TvInputManager.TIME_SHIFT_INVALID_TIME) {
91             return DVR_WATCHED_STATUS_NEW;
92         } else if (watchedPosition > recordedProgram
93                 .getDurationMillis() * DVR_WATCHED_THRESHOLD_RATE) {
94             return DVR_WATCHED_STATUS_WATCHED;
95         } else {
96             return DVR_WATCHED_STATUS_WATCHING;
97         }
98     }
99 
100     /**
101      * Adds {@link WatchedPositionChangedListener}.
102      */
addListener(WatchedPositionChangedListener listener, long recordedProgramId)103     public void addListener(WatchedPositionChangedListener listener, long recordedProgramId) {
104         if (recordedProgramId == RecordedProgram.ID_NOT_SET) {
105             return;
106         }
107         Set<WatchedPositionChangedListener> listenerSet = mListeners.get(recordedProgramId);
108         if (listenerSet == null) {
109             listenerSet = new CopyOnWriteArraySet<>();
110             mListeners.put(recordedProgramId, listenerSet);
111         }
112         listenerSet.add(listener);
113     }
114 
115     /**
116      * Removes {@link WatchedPositionChangedListener}.
117      */
removeListener(WatchedPositionChangedListener listener)118     public void removeListener(WatchedPositionChangedListener listener) {
119         for (long recordedProgramId : new ArrayList<>(mListeners.keySet())) {
120             removeListener(listener, recordedProgramId);
121         }
122     }
123 
124     /**
125      * Removes {@link WatchedPositionChangedListener}.
126      */
removeListener(WatchedPositionChangedListener listener, long recordedProgramId)127     public void removeListener(WatchedPositionChangedListener listener, long recordedProgramId) {
128         Set<WatchedPositionChangedListener> listenerSet = mListeners.get(recordedProgramId);
129         if (listenerSet == null) {
130             return;
131         }
132         listenerSet.remove(listener);
133         if (listenerSet.isEmpty()) {
134             mListeners.remove(recordedProgramId);
135         }
136     }
137 
notifyWatchedPositionChanged(long recordedProgramId, long positionMs)138     private void notifyWatchedPositionChanged(long recordedProgramId, long positionMs) {
139         Set<WatchedPositionChangedListener> listenerSet = mListeners.get(recordedProgramId);
140         if (listenerSet == null) {
141             return;
142         }
143         for (WatchedPositionChangedListener listener : listenerSet) {
144             listener.onWatchedPositionChanged(recordedProgramId, positionMs);
145         }
146     }
147 
148     public interface WatchedPositionChangedListener {
149         /**
150          * Called when the watched position of some program is changed.
151          */
onWatchedPositionChanged(long recordedProgramId, long positionMs)152         void onWatchedPositionChanged(long recordedProgramId, long positionMs);
153     }
154 }
155