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