ChannelBannerView.java revision aa92e04c7c38bba1456b4f02dc3a764233dff24a
1/*
2 * Copyright (C) 2014 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
17package com.android.tv.ui;
18
19import android.content.Context;
20import android.database.ContentObserver;
21import android.graphics.Bitmap;
22import android.graphics.Paint;
23import android.graphics.Typeface;
24import android.graphics.drawable.Drawable;
25import android.media.tv.TvContract;
26import android.media.tv.TvInputInfo;
27import android.net.Uri;
28import android.os.Handler;
29import android.text.TextUtils;
30import android.text.format.DateFormat;
31import android.util.AttributeSet;
32import android.util.LruCache;
33import android.util.TypedValue;
34import android.view.View;
35import android.widget.ImageView;
36import android.widget.ProgressBar;
37import android.widget.RelativeLayout;
38import android.widget.TextView;
39
40import com.android.tv.R;
41import com.android.tv.data.Channel;
42import com.android.tv.data.ChannelMap;
43import com.android.tv.data.Program;
44import com.android.tv.data.StreamInfo;
45import com.android.tv.util.Utils;
46
47/**
48 * A view to render channel banner.
49 */
50public class ChannelBannerView extends RelativeLayout implements Channel.LoadLogoCallback {
51    private static final int CACHE_SIZE = 10;
52    private TextView mClosedCaptionTextView;
53    private TextView mResolutionTextView;
54    private TextView mAspectRatioTextView;
55    private TextView mAudioChannelTextView;
56    private ProgressBar mRemainingTimeView;
57    private TextView mProgrameDescriptionTextView;
58    private TextView mChannelTextView;
59    private TextView mChannelNameTextView;
60    private ImageView mTvInputLogoImageView;
61    private ImageView mChannelLogoImageView;
62    private TextView mProgramTextView;
63    private TextView mProgramTimeTextView;
64    private View mAnchorView;
65    private Uri mCurrentChannelUri;
66    private final LruCache<TvInputInfo, Drawable> mChannelInfoLogoCache =
67            new LruCache<TvInputInfo, Drawable> (CACHE_SIZE) {
68                @Override
69                protected Drawable create(TvInputInfo info) {
70                    return info.loadIcon(getContext().getPackageManager());
71                }
72            };
73
74    private final ContentObserver mProgramUpdateObserver = new ContentObserver(new Handler()) {
75        @Override
76        public void onChange(boolean selfChange, Uri uri) {
77            // TODO: This {@code uri} argument may be a program which is not related to this
78            // channel. Consider adding channel id as a parameter of program URI to avoid
79            // unnecessary update.
80            post(mProgramUpdateRunnable);
81        }
82    };
83
84    private final Runnable mProgramUpdateRunnable = new Runnable() {
85        @Override
86        public void run() {
87            removeCallbacks(this);
88            updateProgramInfo();
89        }
90    };
91
92    public ChannelBannerView(Context context) {
93        this(context, null);
94    }
95
96    public ChannelBannerView(Context context, AttributeSet attrs) {
97        super(context, attrs, 0);
98    }
99
100    public ChannelBannerView(Context context, AttributeSet attrs, int defStyle) {
101        super(context, attrs, defStyle);
102    }
103
104    @Override
105    protected void onAttachedToWindow() {
106        super.onAttachedToWindow();
107        getContext().getContentResolver().registerContentObserver(TvContract.Programs.CONTENT_URI,
108                true, mProgramUpdateObserver);
109    }
110
111    @Override
112    protected void onDetachedFromWindow() {
113        getContext().getContentResolver().unregisterContentObserver(mProgramUpdateObserver);
114        super.onDetachedFromWindow();
115    }
116
117    @Override
118    protected void onFinishInflate() {
119        super.onFinishInflate();
120
121        mClosedCaptionTextView = (TextView) findViewById(R.id.closed_caption);
122        mResolutionTextView = (TextView) findViewById(R.id.resolution);
123        mAspectRatioTextView = (TextView) findViewById(R.id.aspect_ratio);
124        mAudioChannelTextView = (TextView) findViewById(R.id.audio_channel);
125        mRemainingTimeView = (ProgressBar) findViewById(R.id.remaining_time);
126        mChannelTextView = (TextView) findViewById(R.id.channel_text);
127        mChannelNameTextView = (TextView) findViewById(R.id.channel_name);
128        mTvInputLogoImageView = (ImageView) findViewById(R.id.tvinput_logo);
129        mChannelLogoImageView = (ImageView) findViewById(R.id.channel_logo);
130        mProgramTimeTextView = (TextView) findViewById(R.id.program_time_text);
131        mProgrameDescriptionTextView = (TextView) findViewById(R.id.program_description);
132        mProgramTextView = (TextView) findViewById(R.id.program_text);
133        mAnchorView = findViewById(R.id.anchor);
134    }
135
136    public void updateViews(ChannelMap channelMap, StreamInfo info) {
137        if (channelMap == null || !channelMap.isLoadFinished()) {
138            return;
139        }
140
141        TvInputInfo inputInfo = (info == null) ? null : info.getCurrentTvInputInfo();
142        Drawable tvInputLogo = (inputInfo == null) ? null : mChannelInfoLogoCache.get(inputInfo);
143        if (tvInputLogo != null) {
144            mTvInputLogoImageView.setVisibility(View.VISIBLE);
145            mTvInputLogoImageView.setImageDrawable(tvInputLogo);
146        } else {
147            mTvInputLogoImageView.setVisibility(View.GONE);
148        }
149
150        if (info.hasClosedCaption()) {
151            mClosedCaptionTextView.setVisibility(View.VISIBLE);
152            mClosedCaptionTextView.setText("CC");
153            mClosedCaptionTextView.setVisibility(View.VISIBLE);
154        } else {
155            mClosedCaptionTextView.setVisibility(View.GONE);
156        }
157        if (info.getVideoDefinitionLevel() != StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN) {
158            mResolutionTextView.setVisibility(View.VISIBLE);
159            mResolutionTextView.setText(Utils.getVideoDefinitionLevelString(
160                    info.getVideoDefinitionLevel()));
161            mResolutionTextView.setVisibility(View.VISIBLE);
162        } else {
163            mResolutionTextView.setVisibility(View.GONE);
164        }
165
166        String aspectRatio =
167                Utils.getAspectRatioString(info.getVideoWidth(), info.getVideoHeight());
168        if (!TextUtils.isEmpty(aspectRatio)) {
169            mAspectRatioTextView.setVisibility(View.VISIBLE);
170            mAspectRatioTextView.setText(aspectRatio);
171        } else {
172            mAspectRatioTextView.setVisibility(View.GONE);
173        }
174
175        if (!TextUtils.isEmpty(Utils.getAudioChannelString(info.getAudioChannelCount()))) {
176            mAudioChannelTextView.setVisibility(View.VISIBLE);
177            mAudioChannelTextView.setText(Utils.getAudioChannelString(info.getAudioChannelCount()));
178        } else {
179            mAudioChannelTextView.setVisibility(View.GONE);
180        }
181
182        String displayNumber = channelMap.getCurrentDisplayNumber();
183        if (displayNumber == null) {
184            displayNumber = "";
185        }
186
187        if (displayNumber.length() <= 3) {
188            updateTextView(
189                    mChannelTextView,
190                    R.dimen.channel_banner_title_large_text_size,
191                    R.dimen.channel_banner_title_large_margin_top);
192        } else if (displayNumber.length() <= 4) {
193            updateTextView(
194                    mChannelTextView,
195                    R.dimen.channel_banner_title_medium_text_size,
196                    R.dimen.channel_banner_title_medium_margin_top);
197        } else {
198            updateTextView(
199                    mChannelTextView,
200                    R.dimen.channel_banner_title_small_text_size,
201                    R.dimen.channel_banner_title_small_margin_top);
202        }
203        mChannelTextView.setText(displayNumber);
204
205        String displayName = channelMap.getCurrentDisplayName();
206        if (displayName == null) {
207            displayName = "";
208        }
209        mChannelNameTextView.setText(displayName);
210
211        mCurrentChannelUri = channelMap.getCurrentChannelUri();
212        channelMap.getCurrentChannel().loadLogo(getContext(), this);
213
214        updateProgramInfo();
215    }
216
217    private void updateTextView(TextView textView, int sizeRes, int marginTopRes) {
218        float textSize = getContext().getResources().getDimension(sizeRes);
219        if (textView.getTextSize() != textSize) {
220            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
221        }
222        updateTopMargin(textView, marginTopRes);
223    }
224
225    private void updateTopMargin(View view, int marginTopRes) {
226        LayoutParams lp = (LayoutParams) view.getLayoutParams();
227        int topMargin = (int) getContext().getResources().getDimension(marginTopRes);
228        if (lp.topMargin != topMargin) {
229            lp.topMargin = topMargin;
230            view.setLayoutParams(lp);
231        }
232    }
233
234    @Override
235    public void onLoadLogoFinished(Channel channel, Bitmap logo) {
236        if (logo == null) {
237            mChannelLogoImageView.setVisibility(View.GONE);
238        } else {
239            mChannelLogoImageView.setImageBitmap(logo);
240            mChannelLogoImageView.setVisibility(View.VISIBLE);
241        }
242    }
243
244    private String getFormattedTimeString(long time) {
245        return DateFormat.format(
246                getContext().getString(R.string.channel_banner_time_format), time).toString();
247    }
248
249    private void updateProgramInfo() {
250        if (mCurrentChannelUri == null) {
251            handleNoProgramInformation();
252            return;
253        }
254
255        Program program = Utils.getCurrentProgram(getContext(), mCurrentChannelUri);
256        if (program == null) {
257            handleNoProgramInformation();
258            return;
259        }
260
261        String title = program.getTitle();
262        if (!TextUtils.isEmpty(title)) {
263            int width = mProgramTextView.getWidth();
264            if (width == 0) {
265                post(mProgramUpdateRunnable);
266            }
267            float largeTextSize = getContext().getResources().getDimension(
268                    R.dimen.channel_banner_program_large_text_size);
269            Typeface font = mProgramTextView.getTypeface();
270            int estimatedLineCount = estimateLineCount(title, font, largeTextSize, width);
271            boolean oneline = true;
272            if (estimatedLineCount > 1) {
273                updateTextView(
274                        mProgramTextView,
275                        R.dimen.channel_banner_program_medium_text_size,
276                        R.dimen.channel_banner_program_medium_margin_top);
277                float mediumTextSize = getContext().getResources().getDimension(
278                        R.dimen.channel_banner_program_medium_text_size);
279                if (estimateLineCount(title, font, mediumTextSize, width) > 1) {
280                    oneline = false;
281                }
282            } else {
283                updateTextView(
284                        mProgramTextView,
285                        R.dimen.channel_banner_program_large_text_size,
286                        R.dimen.channel_banner_program_large_margin_top);
287            }
288            updateTopMargin(mAnchorView, oneline
289                    ? R.dimen.channel_banner_anchor_one_line_y
290                    : R.dimen.channel_banner_anchor_two_line_y);
291            mProgramTextView.setText(title);
292
293            long startTime = program.getStartTimeUtcMillis();
294            long endTime = program.getEndTimeUtcMillis();
295            if (startTime > 0 && endTime > 0) {
296                mProgramTimeTextView.setVisibility(View.VISIBLE);
297                mRemainingTimeView.setVisibility(View.VISIBLE);
298
299                String startTimeText = getFormattedTimeString(startTime);
300                String endTimeText = getFormattedTimeString(endTime);
301
302                mProgramTimeTextView.setText(getContext().getString(
303                        R.string.channel_banner_program_time_format, startTimeText, endTimeText));
304
305                long currTime = System.currentTimeMillis();
306                if (currTime <= startTime) {
307                    mRemainingTimeView.setProgress(0);
308                } else if (currTime >= endTime) {
309                    mRemainingTimeView.setProgress(100);
310                } else {
311                    mRemainingTimeView.setProgress(
312                            (int) (100 *(currTime - startTime) / (endTime - startTime)));
313                }
314            } else {
315                mProgramTimeTextView.setVisibility(View.GONE);
316                mRemainingTimeView.setVisibility(View.GONE);
317            }
318        } else {
319            mProgramTextView.setText(getContext().getString(R.string.channel_banner_no_title));
320            mProgramTimeTextView.setVisibility(View.GONE);
321            mRemainingTimeView.setVisibility(View.GONE);
322        }
323        if (!TextUtils.isEmpty(program.getDescription())) {
324            mProgrameDescriptionTextView.setVisibility(View.VISIBLE);
325            mProgrameDescriptionTextView.setText(program.getDescription());
326        } else {
327            mProgrameDescriptionTextView.setVisibility(View.GONE);
328        }
329    }
330
331    private void handleNoProgramInformation() {
332        mProgramTextView.setText(getContext().getString(R.string.channel_banner_no_title));
333        mProgramTimeTextView.setVisibility(View.GONE);
334        mRemainingTimeView.setVisibility(View.GONE);
335        mProgrameDescriptionTextView.setVisibility(View.GONE);
336    }
337
338    private int estimateLineCount(String str, Typeface font, float textSize, int width) {
339        if (width == 0) {
340            return -1;
341        }
342        Paint paint = new Paint();
343        paint.setTypeface(font);
344        paint.setTextSize(textSize);
345        // Add +1 to measured size, because number of lines becomes 2
346        // when measured size equals width.
347        return divideRoundUp((int) paint.measureText(str) + 1, width);
348    }
349
350    private int divideRoundUp(int x, int y) {
351        return (x + y - 1) / y;
352    }
353}
354