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