ChannelBannerView.java revision f8367f0d7dc6abc4876b52e04e2530c06464698e
1590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly/*
2590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly * Copyright (C) 2014 The Android Open Source Project
3590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly *
4590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly * Licensed under the Apache License, Version 2.0 (the "License");
5590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly * you may not use this file except in compliance with the License.
6590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly * You may obtain a copy of the License at
7590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly *
8590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly *      http://www.apache.org/licenses/LICENSE-2.0
9590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly *
10590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly * Unless required by applicable law or agreed to in writing, software
11590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly * distributed under the License is distributed on an "AS IS" BASIS,
12590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly * See the License for the specific language governing permissions and
14590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly * limitations under the License.
15590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly */
16590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly
174e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamiltonpackage com.android.tv.ui;
18590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly
196be655c768a82716612c00fdd156254d8dc00f42Jeff Hamiltonimport android.content.Context;
206be655c768a82716612c00fdd156254d8dc00f42Jeff Hamiltonimport android.database.ContentObserver;
214e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamiltonimport android.media.tv.TvContract;
226be655c768a82716612c00fdd156254d8dc00f42Jeff Hamiltonimport android.net.Uri;
236be655c768a82716612c00fdd156254d8dc00f42Jeff Hamiltonimport android.os.Handler;
2474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pellyimport android.text.TextUtils;
256be655c768a82716612c00fdd156254d8dc00f42Jeff Hamiltonimport android.text.format.DateFormat;
26590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pellyimport android.util.AttributeSet;
273dd6c458530476eccb33bc05c9c9cd83823bcf8dNick Pellyimport android.util.Log;
286be655c768a82716612c00fdd156254d8dc00f42Jeff Hamiltonimport android.view.View;
296be655c768a82716612c00fdd156254d8dc00f42Jeff Hamiltonimport android.widget.LinearLayout;
30590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pellyimport android.widget.ProgressBar;
31590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pellyimport android.widget.TextView;
3274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly
336be655c768a82716612c00fdd156254d8dc00f42Jeff Hamiltonimport com.android.tv.R;
3474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pellyimport com.android.tv.data.ChannelMap;
3574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pellyimport com.android.tv.data.Program;
3674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pellyimport com.android.tv.data.StreamInfo;
3774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pellyimport com.android.tv.util.Utils;
3874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly
3974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly/**
4074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * A view to render channel banner.
4174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly */
4274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pellypublic class ChannelBannerView extends LinearLayout {
4374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    private TextView mClosedCaptionTextView;
4474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    private TextView mResolutionTextView;
4574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    private TextView mAspectRatioTextView;
4639cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly    private TextView mAudioChannelTextView;
4774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    private ProgressBar mRemainingTimeView;
4874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    private TextView mProgrameDescriptionTextView;
4974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    private TextView mChannelTextView;
5074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    private TextView mChannelNameTextView;
5174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    private TextView mProgramTextView;
5274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    private TextView mProgramTimeTextView;
5374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    private Uri mCurrentChannelUri;
5465e37ee936e2406e58719584ce30b6aa21792ce0Paul Quei
5574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    private final ContentObserver mProgramUpdateObserver = new ContentObserver(new Handler()) {
5674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        @Override
5774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        public void onChange(boolean selfChange, Uri uri) {
5874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly            updateProgramInfo();
5974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        }
6074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    };
6174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly
6274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    public ChannelBannerView(Context context) {
6374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        super(context);
6474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        mContext = context;
6574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    }
6674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly
676be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton    public ChannelBannerView(Context context, AttributeSet attrs) {
6839cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly        super(context, attrs);
6939cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly        mContext = context;
70590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly    }
716be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton
723dd6c458530476eccb33bc05c9c9cd83823bcf8dNick Pelly    public ChannelBannerView(Context context, AttributeSet attrs, int defStyle) {
733dd6c458530476eccb33bc05c9c9cd83823bcf8dNick Pelly        super(context, attrs, defStyle);
743300e4c3ea2f2317532ded6f9e79d6ad9e038679Martijn Coenen        mContext = context;
753300e4c3ea2f2317532ded6f9e79d6ad9e038679Martijn Coenen    }
763300e4c3ea2f2317532ded6f9e79d6ad9e038679Martijn Coenen
773300e4c3ea2f2317532ded6f9e79d6ad9e038679Martijn Coenen    @Override
783300e4c3ea2f2317532ded6f9e79d6ad9e038679Martijn Coenen    protected void onAttachedToWindow() {
793300e4c3ea2f2317532ded6f9e79d6ad9e038679Martijn Coenen        super.onAttachedToWindow();
80590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly        getContext().getContentResolver().registerContentObserver(TvContract.Programs.CONTENT_URI,
8172df4ea8300ad345eb256cf43d8dae35eef39ba2Martijn Coenen                true, mProgramUpdateObserver);
8272df4ea8300ad345eb256cf43d8dae35eef39ba2Martijn Coenen    }
8372df4ea8300ad345eb256cf43d8dae35eef39ba2Martijn Coenen
846d9fc7e1efa9e99bdab366fc5d579c139fd04e71Martijn Coenen    @Override
856d9fc7e1efa9e99bdab366fc5d579c139fd04e71Martijn Coenen    protected void onDetachedFromWindow() {
866d9fc7e1efa9e99bdab366fc5d579c139fd04e71Martijn Coenen        getContext().getContentResolver().unregisterContentObserver(mProgramUpdateObserver);
873300e4c3ea2f2317532ded6f9e79d6ad9e038679Martijn Coenen        super.onDetachedFromWindow();
883300e4c3ea2f2317532ded6f9e79d6ad9e038679Martijn Coenen    }
893300e4c3ea2f2317532ded6f9e79d6ad9e038679Martijn Coenen
90d27ebf1e698c4e6929cb635768031a2e25b18acdMartijn Coenen    @Override
91d27ebf1e698c4e6929cb635768031a2e25b18acdMartijn Coenen    protected void onFinishInflate() {
92d27ebf1e698c4e6929cb635768031a2e25b18acdMartijn Coenen        super.onFinishInflate();
93f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly
94f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        mClosedCaptionTextView = (TextView) findViewById(R.id.closed_caption);
95f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        mResolutionTextView = (TextView) findViewById(R.id.resolution);
96f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        mAspectRatioTextView = (TextView) findViewById(R.id.aspect_ratio);
97f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        mAudioChannelTextView = (TextView) findViewById(R.id.audio_channel);
98f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        mRemainingTimeView = (ProgressBar) findViewById(R.id.remaining_time);
99f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        mChannelTextView = (TextView) findViewById(R.id.channel_text);
100f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        mChannelNameTextView = (TextView) findViewById(R.id.channel_name);
101f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        mProgramTimeTextView = (TextView) findViewById(R.id.program_time_text);
102f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        mProgrameDescriptionTextView = (TextView) findViewById(R.id.program_description);
103f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        mProgramTextView = (TextView) findViewById(R.id.program_text);
104f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly    }
1055644d0e18ad847b66a8cb4f185cb28edebe75d88Jeff Hamilton
1065644d0e18ad847b66a8cb4f185cb28edebe75d88Jeff Hamilton    public void updateViews(ChannelMap channelMap, StreamInfo info) {
107f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        if (channelMap == null || !channelMap.isLoadFinished()) {
108f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly            return;
109f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        }
110f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly
11174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        if (info.hasClosedCaption()) {
112f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly            mClosedCaptionTextView.setText("CC");
11374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly            mClosedCaptionTextView.setVisibility(View.VISIBLE);
114f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        } else {
11574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly            mClosedCaptionTextView.setVisibility(View.GONE);
116f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        }
11774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        if (info.getVideoDefinitionLevel() != StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN) {
118f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly            mResolutionTextView.setText(Utils.getVideoDefinitionLevelString(
11974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly                    info.getVideoDefinitionLevel()));
120f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly            mResolutionTextView.setVisibility(View.VISIBLE);
1215644d0e18ad847b66a8cb4f185cb28edebe75d88Jeff Hamilton        } else {
1225644d0e18ad847b66a8cb4f185cb28edebe75d88Jeff Hamilton            mResolutionTextView.setVisibility(View.GONE);
1235644d0e18ad847b66a8cb4f185cb28edebe75d88Jeff Hamilton        }
1245644d0e18ad847b66a8cb4f185cb28edebe75d88Jeff Hamilton        // TODO: implement aspect ratio.
1255644d0e18ad847b66a8cb4f185cb28edebe75d88Jeff Hamilton        mAspectRatioTextView.setVisibility(View.GONE);
126d27ebf1e698c4e6929cb635768031a2e25b18acdMartijn Coenen        if (!TextUtils.isEmpty(Utils.getAudioChannelString(info.getAudioChannelCount()))) {
1273300e4c3ea2f2317532ded6f9e79d6ad9e038679Martijn Coenen            mAudioChannelTextView.setText(Utils.getAudioChannelString(info.getAudioChannelCount()));
1283300e4c3ea2f2317532ded6f9e79d6ad9e038679Martijn Coenen            mAudioChannelTextView.setVisibility(View.VISIBLE);
129e3f6336bcffc250da90ec864bccfa73ad1d016b9Martijn Coenen        } else {
130d27ebf1e698c4e6929cb635768031a2e25b18acdMartijn Coenen            mAudioChannelTextView.setVisibility(View.GONE);
1316d9fc7e1efa9e99bdab366fc5d579c139fd04e71Martijn Coenen        }
132590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly
13374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        String displayNumber = channelMap.getCurrentDisplayNumber();
13474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        String displayName = channelMap.getCurrentDisplayName();
13574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        if (displayNumber == null) {
13674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly            displayNumber = "";
13774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        }
13874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        if (displayName == null) {
1394e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton            displayName = "";
14074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        }
14174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        mChannelTextView.setText(displayNumber);
1420bec15ebed8b8639076cba184af3235e17f48718Martijn Coenen        mChannelNameTextView.setText(displayName);
1430bec15ebed8b8639076cba184af3235e17f48718Martijn Coenen
1444e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton        mCurrentChannelUri = channelMap.getCurrentChannelUri();
1454e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton        updateProgramInfo();
1464e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton    }
1474e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton
1484e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton    private String getFormattedTimeString(long time) {
1494e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton        return DateFormat.format(
1504e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton                getContext().getString(R.string.channel_banner_time_format), time).toString();
1514e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton    }
1524e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton
1534e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton    public void updateProgramInfo() {
1544e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton        if (mCurrentChannelUri == null) {
155590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly            handleNoProgramInformation();
156590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly            return;
157590b73bc5b8e5f7b59bff1d9264a52388a5162e6Nick Pelly        }
1584e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton
1594e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton        Program program = Utils.getCurrentProgram(mContext, mCurrentChannelUri);
1604e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton        if (program == null) {
1616d9fc7e1efa9e99bdab366fc5d579c139fd04e71Martijn Coenen            handleNoProgramInformation();
1623300e4c3ea2f2317532ded6f9e79d6ad9e038679Martijn Coenen            return;
1633300e4c3ea2f2317532ded6f9e79d6ad9e038679Martijn Coenen        }
164e3f6336bcffc250da90ec864bccfa73ad1d016b9Martijn Coenen        if (!TextUtils.isEmpty(program.getTitle())) {
165d27ebf1e698c4e6929cb635768031a2e25b18acdMartijn Coenen            mProgramTextView.setText(program.getTitle());
1666d9fc7e1efa9e99bdab366fc5d579c139fd04e71Martijn Coenen
1673300e4c3ea2f2317532ded6f9e79d6ad9e038679Martijn Coenen            long startTime = program.getStartTimeUtcMillis();
1686d9fc7e1efa9e99bdab366fc5d579c139fd04e71Martijn Coenen            long endTime = program.getEndTimeUtcMillis();
1693300e4c3ea2f2317532ded6f9e79d6ad9e038679Martijn Coenen            if (startTime > 0 && endTime > 0) {
1706be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton                mRemainingTimeView.setVisibility(View.VISIBLE);
171a926540d5455a973dd8ca19c00c108620d9c68c2Sylvain Fonteneau
1726be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton                String startTimeText = getFormattedTimeString(startTime);
17374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly                String endTimeText = getFormattedTimeString(endTime);
17474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly
17574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly                mProgramTimeTextView.setText(mContext.getString(
17674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly                        R.string.channel_banner_program_time_format, startTimeText, endTimeText));
17774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly
178a032783241cbbed47ed05df32c56298ee0f9902bMartijn Coenen                long currTime = System.currentTimeMillis();
179a032783241cbbed47ed05df32c56298ee0f9902bMartijn Coenen                if (currTime <= startTime) {
180a032783241cbbed47ed05df32c56298ee0f9902bMartijn Coenen                    mRemainingTimeView.setProgress(0);
18174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly                } else if (currTime >= endTime) {
182a032783241cbbed47ed05df32c56298ee0f9902bMartijn Coenen                    mRemainingTimeView.setProgress(100);
1836be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton                } else {
184e3f6336bcffc250da90ec864bccfa73ad1d016b9Martijn Coenen                    mRemainingTimeView.setProgress(
185e3f6336bcffc250da90ec864bccfa73ad1d016b9Martijn Coenen                            (int) (100 *(currTime - startTime) / (endTime - startTime)));
186a926540d5455a973dd8ca19c00c108620d9c68c2Sylvain Fonteneau                }
187a926540d5455a973dd8ca19c00c108620d9c68c2Sylvain Fonteneau            } else {
188a926540d5455a973dd8ca19c00c108620d9c68c2Sylvain Fonteneau                mProgramTimeTextView.setVisibility(View.GONE);
18974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly                mRemainingTimeView.setVisibility(View.GONE);
19074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly            }
191ddbb2c997b8462d7242b50ea1689a53122d4fce4Nick Pelly        }
192ddbb2c997b8462d7242b50ea1689a53122d4fce4Nick Pelly        if (!TextUtils.isEmpty(program.getDescription())) {
19374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly            mProgrameDescriptionTextView.setText(program.getDescription());
19474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        }
19574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    }
19674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly
19774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly    private void handleNoProgramInformation() {
19874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly        mProgramTextView.setText(mContext.getString(R.string.channel_banner_no_title));
199d27ebf1e698c4e6929cb635768031a2e25b18acdMartijn Coenen        mProgramTimeTextView.setVisibility(View.GONE);
200f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        mRemainingTimeView.setVisibility(View.GONE);
201f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly        mProgrameDescriptionTextView.setVisibility(View.GONE);
202f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly    }
203f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly}
204f003e26df96067b4b136f0859012cb7ec3ed930fNick Pelly