Home | History | Annotate | Download | only in data
      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.data;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.pm.PackageManager;
     22 import android.database.Cursor;
     23 import android.media.tv.TvContract;
     24 import android.media.tv.TvInputInfo;
     25 import android.net.Uri;
     26 import android.support.annotation.Nullable;
     27 import android.support.annotation.UiThread;
     28 import android.support.annotation.VisibleForTesting;
     29 import android.text.TextUtils;
     30 import android.util.Log;
     31 
     32 import com.android.tv.common.TvCommonConstants;
     33 import com.android.tv.util.ImageLoader;
     34 import com.android.tv.util.TvInputManagerHelper;
     35 import com.android.tv.util.Utils;
     36 
     37 import java.net.URISyntaxException;
     38 import java.util.Comparator;
     39 import java.util.HashMap;
     40 import java.util.Map;
     41 import java.util.Objects;
     42 
     43 /**
     44  * A convenience class to create and insert channel entries into the database.
     45  */
     46 public final class Channel {
     47     private static final String TAG = "Channel";
     48 
     49     public static final long INVALID_ID = -1;
     50     public static final int LOAD_IMAGE_TYPE_CHANNEL_LOGO = 1;
     51     public static final int LOAD_IMAGE_TYPE_APP_LINK_ICON = 2;
     52     public static final int LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART = 3;
     53 
     54     /**
     55      * When a TIS doesn't provide any information about app link, and it doesn't have a leanback
     56      * launch intent, there will be no app link card for the TIS.
     57      */
     58     public static final int APP_LINK_TYPE_NONE = -1;
     59     /**
     60      * When a TIS provide a specific app link information, the app link card will be
     61      * {@code APP_LINK_TYPE_CHANNEL} which contains all the provided information.
     62      */
     63     public static final int APP_LINK_TYPE_CHANNEL = 1;
     64     /**
     65      * When a TIS doesn't provide a specific app link information, but the app has a leanback launch
     66      * intent, the app link card will be {@code APP_LINK_TYPE_APP} which launches the application.
     67      */
     68     public static final int APP_LINK_TYPE_APP = 2;
     69 
     70     private static final int APP_LINK_TYPE_NOT_SET = 0;
     71     private static final String INVALID_PACKAGE_NAME = "packageName";
     72 
     73     public static final String[] PROJECTION = {
     74             // Columns must match what is read in Channel.fromCursor()
     75             TvContract.Channels._ID,
     76             TvContract.Channels.COLUMN_PACKAGE_NAME,
     77             TvContract.Channels.COLUMN_INPUT_ID,
     78             TvContract.Channels.COLUMN_TYPE,
     79             TvContract.Channels.COLUMN_DISPLAY_NUMBER,
     80             TvContract.Channels.COLUMN_DISPLAY_NAME,
     81             TvContract.Channels.COLUMN_DESCRIPTION,
     82             TvContract.Channels.COLUMN_VIDEO_FORMAT,
     83             TvContract.Channels.COLUMN_BROWSABLE,
     84             TvContract.Channels.COLUMN_LOCKED,
     85             TvContract.Channels.COLUMN_APP_LINK_TEXT,
     86             TvContract.Channels.COLUMN_APP_LINK_COLOR,
     87             TvContract.Channels.COLUMN_APP_LINK_ICON_URI,
     88             TvContract.Channels.COLUMN_APP_LINK_POSTER_ART_URI,
     89             TvContract.Channels.COLUMN_APP_LINK_INTENT_URI,
     90     };
     91 
     92     /**
     93      * Creates {@code Channel} object from cursor.
     94      *
     95      * <p>The query that created the cursor MUST use {@link #PROJECTION}
     96      *
     97      */
     98     public static Channel fromCursor(Cursor cursor) {
     99         // Columns read must match the order of {@link #PROJECTION}
    100         Channel channel = new Channel();
    101         int index = 0;
    102         channel.mId = cursor.getLong(index++);
    103         channel.mPackageName = Utils.intern(cursor.getString(index++));
    104         channel.mInputId = Utils.intern(cursor.getString(index++));
    105         channel.mType = Utils.intern(cursor.getString(index++));
    106         channel.mDisplayNumber = cursor.getString(index++);
    107         channel.mDisplayName = cursor.getString(index++);
    108         channel.mDescription = cursor.getString(index++);
    109         channel.mVideoFormat = Utils.intern(cursor.getString(index++));
    110         channel.mBrowsable = cursor.getInt(index++) == 1;
    111         channel.mLocked = cursor.getInt(index++) == 1;
    112         channel.mAppLinkText = cursor.getString(index++);
    113         channel.mAppLinkColor = cursor.getInt(index++);
    114         channel.mAppLinkIconUri = cursor.getString(index++);
    115         channel.mAppLinkPosterArtUri = cursor.getString(index++);
    116         channel.mAppLinkIntentUri = cursor.getString(index++);
    117         return channel;
    118     }
    119 
    120     /**
    121      * Creates a {@link Channel} object from the DVR database.
    122      */
    123     public static Channel fromDvrCursor(Cursor c) {
    124         Channel channel = new Channel();
    125         int index = -1;
    126         channel.mDvrId = c.getLong(++index);
    127         return channel;
    128     }
    129 
    130     /** ID of this channel. Matches to BaseColumns._ID. */
    131     private long mId;
    132 
    133     private String mPackageName;
    134     private String mInputId;
    135     private String mType;
    136     private String mDisplayNumber;
    137     private String mDisplayName;
    138     private String mDescription;
    139     private String mVideoFormat;
    140     private boolean mBrowsable;
    141     private boolean mLocked;
    142     private boolean mIsPassthrough;
    143     private String mAppLinkText;
    144     private int mAppLinkColor;
    145     private String mAppLinkIconUri;
    146     private String mAppLinkPosterArtUri;
    147     private String mAppLinkIntentUri;
    148     private Intent mAppLinkIntent;
    149     private int mAppLinkType;
    150 
    151     private long mDvrId;
    152 
    153     private Channel() {
    154         // Do nothing.
    155     }
    156 
    157     public long getId() {
    158         return mId;
    159     }
    160 
    161     public Uri getUri() {
    162         if (isPassthrough()) {
    163             return TvContract.buildChannelUriForPassthroughInput(mInputId);
    164         } else {
    165             return TvContract.buildChannelUri(mId);
    166         }
    167     }
    168 
    169     public String getPackageName() {
    170         return mPackageName;
    171     }
    172 
    173     public String getInputId() {
    174         return mInputId;
    175     }
    176 
    177     public String getType() {
    178         return mType;
    179     }
    180 
    181     public String getDisplayNumber() {
    182         return mDisplayNumber;
    183     }
    184 
    185     @Nullable
    186     public String getDisplayName() {
    187         return mDisplayName;
    188     }
    189 
    190     @VisibleForTesting
    191     public String getDescription() {
    192         return mDescription;
    193     }
    194 
    195     public String getVideoFormat() {
    196         return mVideoFormat;
    197     }
    198 
    199     public boolean isPassthrough() {
    200         return mIsPassthrough;
    201     }
    202 
    203     /**
    204      * Gets identification text for displaying or debugging.
    205      * It's made from Channels' display number plus their display name.
    206      */
    207     public String getDisplayText() {
    208         return TextUtils.isEmpty(mDisplayName) ? mDisplayNumber
    209                 : mDisplayNumber + " " + mDisplayName;
    210     }
    211 
    212     public String getAppLinkText() {
    213         return mAppLinkText;
    214     }
    215 
    216     public int getAppLinkColor() {
    217         return mAppLinkColor;
    218     }
    219 
    220     public String getAppLinkIconUri() {
    221         return mAppLinkIconUri;
    222     }
    223 
    224     public String getAppLinkPosterArtUri() {
    225         return mAppLinkPosterArtUri;
    226     }
    227 
    228     public String getAppLinkIntentUri() {
    229         return mAppLinkIntentUri;
    230     }
    231 
    232     /**
    233      * Returns an ID in DVR database.
    234      */
    235     public long getDvrId() {
    236         return mDvrId;
    237     }
    238 
    239     /**
    240      * Checks whether this channel is physical tuner channel or not.
    241      */
    242     public boolean isPhysicalTunerChannel() {
    243         return !TextUtils.isEmpty(mType) && !TvContract.Channels.TYPE_OTHER.equals(mType);
    244     }
    245 
    246     /**
    247      * Checks if two channels equal by checking ids.
    248      */
    249     @Override
    250     public boolean equals(Object o) {
    251         if (!(o instanceof Channel)) {
    252             return false;
    253         }
    254         Channel other = (Channel) o;
    255         // All pass-through TV channels have INVALID_ID value for mId.
    256         return mId == other.mId && TextUtils.equals(mInputId, other.mInputId)
    257                 && mIsPassthrough == other.mIsPassthrough;
    258     }
    259 
    260     @Override
    261     public int hashCode() {
    262         return Objects.hash(mId, mInputId, mIsPassthrough);
    263     }
    264 
    265     public boolean isBrowsable() {
    266         return mBrowsable;
    267     }
    268 
    269     public boolean isLocked() {
    270         return mLocked;
    271     }
    272 
    273     public void setBrowsable(boolean browsable) {
    274         mBrowsable = browsable;
    275     }
    276 
    277     public void setLocked(boolean locked) {
    278         mLocked = locked;
    279     }
    280 
    281     /**
    282      * Check whether {@code other} has same read-only channel info as this. But, it cannot check two
    283      * channels have same logos. It also excludes browsable and locked, because two fields are
    284      * changed by TV app.
    285      */
    286     public boolean hasSameReadOnlyInfo(Channel other) {
    287         return other != null
    288                 && Objects.equals(mId, other.mId)
    289                 && Objects.equals(mPackageName, other.mPackageName)
    290                 && Objects.equals(mInputId, other.mInputId)
    291                 && Objects.equals(mType, other.mType)
    292                 && Objects.equals(mDisplayNumber, other.mDisplayNumber)
    293                 && Objects.equals(mDisplayName, other.mDisplayName)
    294                 && Objects.equals(mDescription, other.mDescription)
    295                 && Objects.equals(mVideoFormat, other.mVideoFormat)
    296                 && mIsPassthrough == other.mIsPassthrough
    297                 && Objects.equals(mAppLinkText, other.mAppLinkText)
    298                 && mAppLinkColor == other.mAppLinkColor
    299                 && Objects.equals(mAppLinkIconUri, other.mAppLinkIconUri)
    300                 && Objects.equals(mAppLinkPosterArtUri, other.mAppLinkPosterArtUri)
    301                 && Objects.equals(mAppLinkIntentUri, other.mAppLinkIntentUri);
    302     }
    303 
    304     @Override
    305     public String toString() {
    306         return "Channel{"
    307                 + "id=" + mId
    308                 + ", packageName=" + mPackageName
    309                 + ", inputId=" + mInputId
    310                 + ", type=" + mType
    311                 + ", displayNumber=" + mDisplayNumber
    312                 + ", displayName=" + mDisplayName
    313                 + ", description=" + mDescription
    314                 + ", videoFormat=" + mVideoFormat
    315                 + ", isPassthrough=" + mIsPassthrough
    316                 + ", browsable=" + mBrowsable
    317                 + ", locked=" + mLocked
    318                 + ", appLinkText=" + mAppLinkText + "}";
    319     }
    320 
    321     void copyFrom(Channel other) {
    322         if (this == other) {
    323             return;
    324         }
    325         mId = other.mId;
    326         mPackageName = other.mPackageName;
    327         mInputId = other.mInputId;
    328         mType = other.mType;
    329         mDisplayNumber = other.mDisplayNumber;
    330         mDisplayName = other.mDisplayName;
    331         mDescription = other.mDescription;
    332         mVideoFormat = other.mVideoFormat;
    333         mIsPassthrough = other.mIsPassthrough;
    334         mBrowsable = other.mBrowsable;
    335         mLocked = other.mLocked;
    336         mAppLinkText = other.mAppLinkText;
    337         mAppLinkColor = other.mAppLinkColor;
    338         mAppLinkIconUri = other.mAppLinkIconUri;
    339         mAppLinkPosterArtUri = other.mAppLinkPosterArtUri;
    340         mAppLinkIntentUri = other.mAppLinkIntentUri;
    341         mAppLinkIntent = other.mAppLinkIntent;
    342         mAppLinkType = other.mAppLinkType;
    343     }
    344 
    345     /**
    346      * Creates a channel for a passthrough TV input.
    347      */
    348     public static Channel createPassthroughChannel(Uri uri) {
    349         if (!TvContract.isChannelUriForPassthroughInput(uri)) {
    350             throw new IllegalArgumentException("URI is not a passthrough channel URI");
    351         }
    352         String inputId = uri.getPathSegments().get(1);
    353         return createPassthroughChannel(inputId);
    354     }
    355 
    356     /**
    357      * Creates a channel for a passthrough TV input with {@code inputId}.
    358      */
    359     public static Channel createPassthroughChannel(String inputId) {
    360         return new Builder()
    361                 .setInputId(inputId)
    362                 .setPassthrough(true)
    363                 .build();
    364     }
    365 
    366     /**
    367      * Checks whether the channel is valid or not.
    368      */
    369     public static boolean isValid(Channel channel) {
    370         return channel != null && (channel.mId != INVALID_ID || channel.mIsPassthrough);
    371     }
    372 
    373     /**
    374      * Builder class for {@code Channel}.
    375      * Suppress using this outside of ChannelDataManager
    376      * so Channels could be managed by ChannelDataManager.
    377      */
    378     public static final class Builder {
    379         private final Channel mChannel;
    380 
    381         public Builder() {
    382             mChannel = new Channel();
    383             // Fill initial data.
    384             mChannel.mId = INVALID_ID;
    385             mChannel.mPackageName = INVALID_PACKAGE_NAME;
    386             mChannel.mInputId = "inputId";
    387             mChannel.mType = "type";
    388             mChannel.mDisplayNumber = "0";
    389             mChannel.mDisplayName = "name";
    390             mChannel.mDescription = "description";
    391             mChannel.mBrowsable = true;
    392             mChannel.mLocked = false;
    393             mChannel.mIsPassthrough = false;
    394         }
    395 
    396         public Builder(Channel other) {
    397             mChannel = new Channel();
    398             mChannel.copyFrom(other);
    399         }
    400 
    401         @VisibleForTesting
    402         public Builder setId(long id) {
    403             mChannel.mId = id;
    404             return this;
    405         }
    406 
    407         @VisibleForTesting
    408         public Builder setPackageName(String packageName) {
    409             mChannel.mPackageName = packageName;
    410             return this;
    411         }
    412 
    413         public Builder setInputId(String inputId) {
    414             mChannel.mInputId = inputId;
    415             return this;
    416         }
    417 
    418         public Builder setType(String type) {
    419             mChannel.mType = type;
    420             return this;
    421         }
    422 
    423         @VisibleForTesting
    424         public Builder setDisplayNumber(String displayNumber) {
    425             mChannel.mDisplayNumber = displayNumber;
    426             return this;
    427         }
    428 
    429         @VisibleForTesting
    430         public Builder setDisplayName(String displayName) {
    431             mChannel.mDisplayName = displayName;
    432             return this;
    433         }
    434 
    435         @VisibleForTesting
    436         public Builder setDescription(String description) {
    437             mChannel.mDescription = description;
    438             return this;
    439         }
    440 
    441         public Builder setVideoFormat(String videoFormat) {
    442             mChannel.mVideoFormat = videoFormat;
    443             return this;
    444         }
    445 
    446         public Builder setBrowsable(boolean browsable) {
    447             mChannel.mBrowsable = browsable;
    448             return this;
    449         }
    450 
    451         public Builder setLocked(boolean locked) {
    452             mChannel.mLocked = locked;
    453             return this;
    454         }
    455 
    456         public Builder setPassthrough(boolean isPassthrough) {
    457             mChannel.mIsPassthrough = isPassthrough;
    458             return this;
    459         }
    460 
    461         @VisibleForTesting
    462         public Builder setAppLinkText(String appLinkText) {
    463             mChannel.mAppLinkText = appLinkText;
    464             return this;
    465         }
    466 
    467         public Builder setAppLinkColor(int appLinkColor) {
    468             mChannel.mAppLinkColor = appLinkColor;
    469             return this;
    470         }
    471 
    472         public Builder setAppLinkIconUri(String appLinkIconUri) {
    473             mChannel.mAppLinkIconUri = appLinkIconUri;
    474             return this;
    475         }
    476 
    477         public Builder setAppLinkPosterArtUri(String appLinkPosterArtUri) {
    478             mChannel.mAppLinkPosterArtUri = appLinkPosterArtUri;
    479             return this;
    480         }
    481 
    482         @VisibleForTesting
    483         public Builder setAppLinkIntentUri(String appLinkIntentUri) {
    484             mChannel.mAppLinkIntentUri = appLinkIntentUri;
    485             return this;
    486         }
    487 
    488         public Channel build() {
    489             Channel channel = new Channel();
    490             channel.copyFrom(mChannel);
    491             return channel;
    492         }
    493     }
    494 
    495     /**
    496      * Prefetches the images for this channel.
    497      */
    498     public void prefetchImage(Context context, int type, int maxWidth, int maxHeight) {
    499         String uriString = getImageUriString(type);
    500         if (!TextUtils.isEmpty(uriString)) {
    501             ImageLoader.prefetchBitmap(context, uriString, maxWidth, maxHeight);
    502         }
    503     }
    504 
    505     /**
    506      * Loads the bitmap of this channel and returns it via {@code callback}.
    507      * The loaded bitmap will be cached and resized with given params.
    508      * <p>
    509      * Note that it may directly call {@code callback} if the bitmap is already loaded.
    510      *
    511      * @param context A context.
    512      * @param type The type of bitmap which will be loaded. It should be one of follows:
    513      *        {@link #LOAD_IMAGE_TYPE_CHANNEL_LOGO}, {@link #LOAD_IMAGE_TYPE_APP_LINK_ICON}, or
    514      *        {@link #LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART}.
    515      * @param maxWidth The max width of the loaded bitmap.
    516      * @param maxHeight The max height of the loaded bitmap.
    517      * @param callback A callback which will be called after the loading finished.
    518      */
    519     @UiThread
    520     public void loadBitmap(Context context, final int type, int maxWidth, int maxHeight,
    521             ImageLoader.ImageLoaderCallback callback) {
    522         String uriString = getImageUriString(type);
    523         ImageLoader.loadBitmap(context, uriString, maxWidth, maxHeight, callback);
    524     }
    525 
    526     /**
    527      * Returns the type of app link for this channel.
    528      * It returns {@link #APP_LINK_TYPE_CHANNEL} if the channel has a non null app link text and
    529      * a valid app link intent, it returns {@link #APP_LINK_TYPE_APP} if the input service which
    530      * holds the channel has leanback launch intent, and it returns {@link #APP_LINK_TYPE_NONE}
    531      * otherwise.
    532      */
    533     public int getAppLinkType(Context context) {
    534         if (mAppLinkType == APP_LINK_TYPE_NOT_SET) {
    535             initAppLinkTypeAndIntent(context);
    536         }
    537         return mAppLinkType;
    538     }
    539 
    540     /**
    541      * Returns the app link intent for this channel.
    542      * If the type of app link is {@link #APP_LINK_TYPE_NONE}, it returns {@code null}.
    543      */
    544     public Intent getAppLinkIntent(Context context) {
    545         if (mAppLinkType == APP_LINK_TYPE_NOT_SET) {
    546             initAppLinkTypeAndIntent(context);
    547         }
    548         return mAppLinkIntent;
    549     }
    550 
    551     private void initAppLinkTypeAndIntent(Context context) {
    552         mAppLinkType = APP_LINK_TYPE_NONE;
    553         mAppLinkIntent = null;
    554         PackageManager pm = context.getPackageManager();
    555         if (!TextUtils.isEmpty(mAppLinkText) && !TextUtils.isEmpty(mAppLinkIntentUri)) {
    556             try {
    557                 Intent intent = Intent.parseUri(mAppLinkIntentUri, Intent.URI_INTENT_SCHEME);
    558                 if (intent.resolveActivityInfo(pm, 0) != null) {
    559                     mAppLinkIntent = intent;
    560                     mAppLinkIntent.putExtra(TvCommonConstants.EXTRA_APP_LINK_CHANNEL_URI,
    561                             getUri().toString());
    562                     mAppLinkType = APP_LINK_TYPE_CHANNEL;
    563                     return;
    564                 } else {
    565                     Log.w(TAG, "No activity exists to handle : " + mAppLinkIntentUri);
    566                 }
    567             } catch (URISyntaxException e) {
    568                 Log.w(TAG, "Unable to set app link for " + mAppLinkIntentUri, e);
    569                 // Do nothing.
    570             }
    571         }
    572         if (mPackageName.equals(context.getApplicationContext().getPackageName())) {
    573             return;
    574         }
    575         mAppLinkIntent = pm.getLeanbackLaunchIntentForPackage(mPackageName);
    576         if (mAppLinkIntent != null) {
    577             mAppLinkIntent.putExtra(TvCommonConstants.EXTRA_APP_LINK_CHANNEL_URI,
    578                     getUri().toString());
    579             mAppLinkType = APP_LINK_TYPE_APP;
    580         }
    581     }
    582 
    583     private String getImageUriString(int type) {
    584         switch (type) {
    585             case LOAD_IMAGE_TYPE_CHANNEL_LOGO:
    586                 return TvContract.buildChannelLogoUri(mId).toString();
    587             case LOAD_IMAGE_TYPE_APP_LINK_ICON:
    588                 return mAppLinkIconUri;
    589             case LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART:
    590                 return mAppLinkPosterArtUri;
    591         }
    592         return null;
    593     }
    594 
    595     public static class DefaultComparator implements Comparator<Channel> {
    596         private final Context mContext;
    597         private final TvInputManagerHelper mInputManager;
    598         private final Map<String, String> mInputIdToLabelMap = new HashMap<>();
    599         private boolean mDetectDuplicatesEnabled;
    600 
    601         public DefaultComparator(Context context, TvInputManagerHelper inputManager) {
    602             mContext = context;
    603             mInputManager = inputManager;
    604         }
    605 
    606         public void setDetectDuplicatesEnabled(boolean detectDuplicatesEnabled) {
    607             mDetectDuplicatesEnabled = detectDuplicatesEnabled;
    608         }
    609 
    610         @Override
    611         public int compare(Channel lhs, Channel rhs) {
    612             if (lhs == rhs) {
    613                 return 0;
    614             }
    615             // Put channels from OEM/SOC inputs first.
    616             boolean lhsIsPartner = mInputManager.isPartnerInput(lhs.getInputId());
    617             boolean rhsIsPartner = mInputManager.isPartnerInput(rhs.getInputId());
    618             if (lhsIsPartner != rhsIsPartner) {
    619                 return lhsIsPartner ? -1 : 1;
    620             }
    621             // Compare the input labels.
    622             String lhsLabel = getInputLabelForChannel(lhs);
    623             String rhsLabel = getInputLabelForChannel(rhs);
    624             int result = lhsLabel == null ? (rhsLabel == null ? 0 : 1) : rhsLabel == null ? -1
    625                     : lhsLabel.compareTo(rhsLabel);
    626             if (result != 0) {
    627                 return result;
    628             }
    629             // Compare the input IDs. The input IDs cannot be null.
    630             result = lhs.getInputId().compareTo(rhs.getInputId());
    631             if (result != 0) {
    632                 return result;
    633             }
    634             // Compare the channel numbers if both channels belong to the same input.
    635             result = ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber());
    636             if (mDetectDuplicatesEnabled && result == 0) {
    637                 Log.w(TAG, "Duplicate channels detected! - \""
    638                         + lhs.getDisplayText() + "\" and \"" + rhs.getDisplayText() + "\"");
    639             }
    640             return result;
    641         }
    642 
    643         @VisibleForTesting
    644         String getInputLabelForChannel(Channel channel) {
    645             String label = mInputIdToLabelMap.get(channel.getInputId());
    646             if (label == null) {
    647                 TvInputInfo info = mInputManager.getTvInputInfo(channel.getInputId());
    648                 if (info != null) {
    649                     label = Utils.loadLabel(mContext, info);
    650                     if (label != null) {
    651                         mInputIdToLabelMap.put(channel.getInputId(), label);
    652                     }
    653                 }
    654             }
    655             return label;
    656         }
    657     }
    658 }
    659