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