aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/menu/ChannelsPosterPrefetcher.java
blob: 9cecb9c08530f9cd419b29e7ff46f7cf3ac5ba4f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/*
 * Copyright (C) 2015 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.menu;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.util.Log;
import com.android.tv.R;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.WeakHandler;
import com.android.tv.data.ChannelImpl;
import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
import com.android.tv.data.api.Channel;
import java.util.List;

/** A poster image prefetcher to show the program poster art in the Channels row faster. */
public class ChannelsPosterPrefetcher {
    private static final String TAG = "PosterPrefetcher";
    private static final boolean DEBUG = false;
    private static final int MSG_PREFETCH_IMAGE = 1000;
    private static final int ONDEMAND_POSTER_PREFETCH_DELAY_MILLIS = 500; // 500 milliseconds

    private final ProgramDataManager mProgramDataManager;
    private final ChannelsRowAdapter mChannelsAdapter;
    private final int mPosterArtWidth;
    private final int mPosterArtHeight;
    private final Context mContext;
    private final Handler mHandler = new PrefetchHandler(this);

    private boolean isCanceled;

    /** Create {@link ChannelsPosterPrefetcher} object with given parameters. */
    public ChannelsPosterPrefetcher(
            Context context, ProgramDataManager programDataManager, ChannelsRowAdapter adapter) {
        mProgramDataManager = programDataManager;
        mChannelsAdapter = adapter;
        mPosterArtWidth =
                context.getResources().getDimensionPixelSize(R.dimen.card_image_layout_width);
        mPosterArtHeight =
                context.getResources().getDimensionPixelSize(R.dimen.card_image_layout_height);
        mContext = context.getApplicationContext();
    }

    /** Start prefetching of program poster art of recommendation. */
    public void prefetch() {
        SoftPreconditions.checkState(!isCanceled, TAG, "Prefetch called after cancel was called.");
        if (isCanceled) {
            return;
        }
        if (DEBUG) Log.d(TAG, "startPrefetching()");
        /*
         * When a user browse channels, this method could be called many times. We don't need to
         * prefetch the intermediate channels. So ignore previous schedule.
         */
        mHandler.removeMessages(MSG_PREFETCH_IMAGE);
        mHandler.sendMessageDelayed(
                mHandler.obtainMessage(MSG_PREFETCH_IMAGE), ONDEMAND_POSTER_PREFETCH_DELAY_MILLIS);
    }

    /** Cancels pending and current prefetch requests. */
    public void cancel() {
        isCanceled = true;
        mHandler.removeCallbacksAndMessages(null);
    }

    @MainThread // ProgramDataManager.getCurrentProgram must be called from the main thread
    private void doPrefetchImages() {
        if (DEBUG) Log.d(TAG, "doPrefetchImages() started");

        // This executes on the main thread, but since the item list is expected to be about 5 items
        // and ImageLoader spawns an async task so this is fast enough. 1 ms in local testing.
        List<ChannelsRowItem> items = mChannelsAdapter.getItemList();
        if (items != null) {
            for (ChannelsRowItem item : items) {
                if (isCanceled) {
                    return;
                }
                Channel channel = item.getChannel();
                if (!ChannelImpl.isValid(channel)) {
                    continue;
                }
                channel.prefetchImage(
                        mContext,
                        Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO,
                        mPosterArtWidth,
                        mPosterArtHeight);
                Program program = mProgramDataManager.getCurrentProgram(channel.getId());
                if (program != null) {
                    program.prefetchPosterArt(mContext, mPosterArtWidth, mPosterArtHeight);
                }
            }
        }
        if (DEBUG) {
            Log.d(
                    TAG,
                    "doPrefetchImages() finished. ImageLoader may still have async tasks for "
                            + "channels "
                            + items);
        }
    }

    private static class PrefetchHandler extends WeakHandler<ChannelsPosterPrefetcher> {
        public PrefetchHandler(ChannelsPosterPrefetcher ref) {
            // doPrefetchImages must be called from the main thread.
            super(Looper.getMainLooper(), ref);
        }

        @Override
        @MainThread
        public void handleMessage(Message msg, @NonNull ChannelsPosterPrefetcher prefetcher) {
            switch (msg.what) {
                case MSG_PREFETCH_IMAGE:
                    prefetcher.doPrefetchImages();
                    break;
            }
        }
    }
}