1 /*
2  * Copyright (C) 2015 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.menu;
18 
19 import android.content.Context;
20 import android.os.Handler;
21 import android.os.Looper;
22 import android.os.Message;
23 import android.support.annotation.MainThread;
24 import android.support.annotation.NonNull;
25 import android.util.Log;
26 
27 import com.android.tv.R;
28 import com.android.tv.common.SoftPreconditions;
29 import com.android.tv.common.WeakHandler;
30 import com.android.tv.data.Channel;
31 import com.android.tv.data.Program;
32 import com.android.tv.data.ProgramDataManager;
33 
34 import java.util.List;
35 
36 /**
37  * A poster image prefetcher to show the program poster art in the Channels row faster.
38  */
39 public class ChannelsPosterPrefetcher {
40     private static final String TAG = "PosterPrefetcher";
41     private static final boolean DEBUG = false;
42     private static final int MSG_PREFETCH_IMAGE = 1000;
43     private static final int ONDEMAND_POSTER_PREFETCH_DELAY_MILLIS = 500;  // 500 milliseconds
44 
45     private final ProgramDataManager mProgramDataManager;
46     private final ChannelsRowAdapter mChannelsAdapter;
47     private final int mPosterArtWidth;
48     private final int mPosterArtHeight;
49     private final Context mContext;
50     private final Handler mHandler = new PrefetchHandler(this);
51 
52     private boolean isCanceled;
53 
54     /**
55      * Create {@link ChannelsPosterPrefetcher} object with given parameters.
56      */
ChannelsPosterPrefetcher(Context context, ProgramDataManager programDataManager, ChannelsRowAdapter adapter)57     public ChannelsPosterPrefetcher(Context context, ProgramDataManager programDataManager,
58             ChannelsRowAdapter adapter) {
59         mProgramDataManager = programDataManager;
60         mChannelsAdapter = adapter;
61         mPosterArtWidth = context.getResources().getDimensionPixelSize(
62                 R.dimen.card_image_layout_width);
63         mPosterArtHeight = context.getResources().getDimensionPixelSize(
64                 R.dimen.card_image_layout_height);
65         mContext = context.getApplicationContext();
66     }
67 
68     /**
69      * Start prefetching of program poster art of recommendation.
70      */
prefetch()71     public void prefetch() {
72         SoftPreconditions.checkState(!isCanceled, TAG, "Prefetch called after cancel was called.");
73         if (isCanceled) {
74             return;
75         }
76         if (DEBUG) Log.d(TAG, "startPrefetching()");
77         /*
78          * When a user browse channels, this method could be called many times. We don't need to
79          * prefetch the intermediate channels. So ignore previous schedule.
80          */
81         mHandler.removeMessages(MSG_PREFETCH_IMAGE);
82         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PREFETCH_IMAGE),
83                 ONDEMAND_POSTER_PREFETCH_DELAY_MILLIS);
84     }
85 
86     /**
87      * Cancels pending and current prefetch requests.
88      */
cancel()89     public void cancel() {
90         isCanceled = true;
91         mHandler.removeCallbacksAndMessages(null);
92     }
93 
94     @MainThread // ProgramDataManager.getCurrentProgram must be called from the main thread
doPrefetchImages()95     private void doPrefetchImages() {
96         if (DEBUG) Log.d(TAG, "doPrefetchImages() started");
97 
98         // This executes on the main thread, but since the item list is expected to be about 5 items
99         // and ImageLoader spawns an async task so this is fast enough. 1 ms in local testing.
100         List<Channel> channelList = mChannelsAdapter.getItemList();
101         if (channelList != null) {
102             for (Channel channel : channelList) {
103                 if (isCanceled) {
104                     return;
105                 }
106                 if (!Channel.isValid(channel)) {
107                     continue;
108                 }
109                 channel.prefetchImage(mContext, Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO,
110                         mPosterArtWidth, mPosterArtHeight);
111                 Program program = mProgramDataManager.getCurrentProgram(channel.getId());
112                 if (program != null) {
113                     program.prefetchPosterArt(mContext, mPosterArtWidth, mPosterArtHeight);
114                 }
115             }
116         }
117         if (DEBUG) {
118             Log.d(TAG, "doPrefetchImages() finished. ImageLoader may still have async tasks for "
119                             + "channels " + channelList);
120         }
121     }
122 
123     private static class PrefetchHandler extends WeakHandler<ChannelsPosterPrefetcher> {
PrefetchHandler(ChannelsPosterPrefetcher ref)124         public PrefetchHandler(ChannelsPosterPrefetcher ref) {
125             // doPrefetchImages must be called from the main thread.
126             super(Looper.getMainLooper(), ref);
127         }
128 
129         @Override
130         @MainThread
handleMessage(Message msg, @NonNull ChannelsPosterPrefetcher prefetcher)131         public void handleMessage(Message msg, @NonNull ChannelsPosterPrefetcher prefetcher) {
132             switch (msg.what) {
133                 case MSG_PREFETCH_IMAGE:
134                     prefetcher.doPrefetchImages();
135                     break;
136             }
137         }
138     }
139 }
140