ChannelBannerView.java revision 0adb8cd2332d2e61a7ba9fabe14fdfcf0d44da27
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.content.pm.PackageManager;
21import android.content.pm.PackageManager.NameNotFoundException;
22import android.content.pm.ServiceInfo;
23import android.database.ContentObserver;
24import android.graphics.Bitmap;
25import android.graphics.Paint;
26import android.graphics.Rect;
27import android.graphics.drawable.Drawable;
28import android.media.tv.TvContract;
29import android.media.tv.TvInputInfo;
30import android.net.Uri;
31import android.os.Handler;
32import android.text.TextUtils;
33import android.text.format.DateFormat;
34import android.util.AttributeSet;
35import android.util.LruCache;
36import android.util.TypedValue;
37import android.view.View;
38import android.widget.ImageView;
39import android.widget.ProgressBar;
40import android.widget.RelativeLayout;
41import android.widget.TextView;
42
43import com.android.tv.R;
44import com.android.tv.data.Channel;
45import com.android.tv.data.ChannelMap;
46import com.android.tv.data.Program;
47import com.android.tv.data.StreamInfo;
48import com.android.tv.util.Utils;
49
50/**
51 * A view to render channel banner.
52 */
53public class ChannelBannerView extends RelativeLayout implements Channel.LoadLogoCallback {
54    private static final int CACHE_SIZE = 10;
55    private TextView mClosedCaptionTextView;
56    private TextView mResolutionTextView;
57    private TextView mAspectRatioTextView;
58    private TextView mAudioChannelTextView;
59    private ProgressBar mRemainingTimeView;
60    private TextView mProgrameDescriptionTextView;
61    private TextView mChannelTextView;
62    private TextView mChannelNameTextView;
63    private ImageView mTvInputLogoImageView;
64    private ImageView mChannelLogoImageView;
65    private TextView mProgramTextView;
66    private TextView mProgramTimeTextView;
67    private View mAnchorView;
68    private Uri mCurrentChannelUri;
69    private final LruCache<TvInputInfo, Drawable> mChannelInfoLogoCache =
70            new LruCache<TvInputInfo, Drawable> (CACHE_SIZE) {
71                @Override
72                protected Drawable create(TvInputInfo info) {
73                    try {
74                        PackageManager pm = getContext().getPackageManager();
75                        ServiceInfo serviceInfo = pm.getServiceInfo(info.getComponent(), 0);
76                        if (serviceInfo != null) {
77                            return serviceInfo.loadLogo(pm);
78                        }
79                    } catch (NameNotFoundException e) {
80                    }
81                    return null;
82                }
83            };
84
85    private final ContentObserver mProgramUpdateObserver = new ContentObserver(new Handler()) {
86        @Override
87        public void onChange(boolean selfChange, Uri uri) {
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    public 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(new Runnable() {
266                    @Override
267                    public void run() {
268                        updateProgramInfo();
269                    }
270                });
271            }
272            float largeTextSize = getContext().getResources().getDimension(
273                    R.dimen.channel_banner_program_large_text_size);
274            int estimatedLineCount = estimateLineCount(title, largeTextSize, width);
275            boolean oneline = true;
276            if (estimatedLineCount > 1) {
277                updateTextView(
278                        mProgramTextView,
279                        R.dimen.channel_banner_program_medium_text_size,
280                        R.dimen.channel_banner_program_medium_margin_top);
281                float mediumTextSize = getContext().getResources().getDimension(
282                        R.dimen.channel_banner_program_medium_text_size);
283                if (estimateLineCount(title, mediumTextSize, width) > 1) {
284                    oneline = false;
285                }
286            } else {
287                updateTextView(
288                        mProgramTextView,
289                        R.dimen.channel_banner_program_large_text_size,
290                        R.dimen.channel_banner_program_large_margin_top);
291            }
292            updateTopMargin(mAnchorView, oneline
293                    ? R.dimen.channel_banner_anchor_one_line_y
294                    : R.dimen.channel_banner_anchor_two_line_y);
295            mProgramTextView.setText(title);
296
297            long startTime = program.getStartTimeUtcMillis();
298            long endTime = program.getEndTimeUtcMillis();
299            if (startTime > 0 && endTime > 0) {
300                mProgramTimeTextView.setVisibility(View.VISIBLE);
301                mRemainingTimeView.setVisibility(View.VISIBLE);
302
303                String startTimeText = getFormattedTimeString(startTime);
304                String endTimeText = getFormattedTimeString(endTime);
305
306                mProgramTimeTextView.setText(getContext().getString(
307                        R.string.channel_banner_program_time_format, startTimeText, endTimeText));
308
309                long currTime = System.currentTimeMillis();
310                if (currTime <= startTime) {
311                    mRemainingTimeView.setProgress(0);
312                } else if (currTime >= endTime) {
313                    mRemainingTimeView.setProgress(100);
314                } else {
315                    mRemainingTimeView.setProgress(
316                            (int) (100 *(currTime - startTime) / (endTime - startTime)));
317                }
318            } else {
319                mProgramTimeTextView.setVisibility(View.GONE);
320                mRemainingTimeView.setVisibility(View.GONE);
321            }
322        } else {
323            mProgramTextView.setText(getContext().getString(R.string.channel_banner_no_title));
324            mProgramTimeTextView.setVisibility(View.GONE);
325            mRemainingTimeView.setVisibility(View.GONE);
326        }
327        if (!TextUtils.isEmpty(program.getDescription())) {
328            mProgrameDescriptionTextView.setVisibility(View.VISIBLE);
329            mProgrameDescriptionTextView.setText(program.getDescription());
330        } else {
331            mProgrameDescriptionTextView.setVisibility(View.GONE);
332        }
333    }
334
335    private void handleNoProgramInformation() {
336        mProgramTextView.setText(getContext().getString(R.string.channel_banner_no_title));
337        mProgramTimeTextView.setVisibility(View.GONE);
338        mRemainingTimeView.setVisibility(View.GONE);
339        mProgrameDescriptionTextView.setVisibility(View.GONE);
340    }
341
342    private int estimateLineCount(String str, float textSize, int width) {
343        if (width == 0) {
344            return -1;
345        }
346        Rect bounds = new Rect();
347        Paint paint = new Paint();
348        paint.setTextSize(textSize);
349        paint.getTextBounds(str, 0, str.length(), bounds);
350        return (bounds.width() + width - 1) / width;
351    }
352}
353