• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
fromCursor(Cursor cursor)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('-'). */
normalizeDisplayNumber(String string)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 
ChannelImpl()155     private ChannelImpl() {
156         // Do nothing.
157     }
158 
159     @Override
getId()160     public long getId() {
161         return mId;
162     }
163 
164     @Override
getUri()165     public Uri getUri() {
166         if (isPassthrough()) {
167             return TvContract.buildChannelUriForPassthroughInput(mInputId);
168         } else {
169             return TvContract.buildChannelUri(mId);
170         }
171     }
172 
173     @Override
getPackageName()174     public String getPackageName() {
175         return mPackageName;
176     }
177 
178     @Override
getInputId()179     public String getInputId() {
180         return mInputId;
181     }
182 
183     @Override
getType()184     public String getType() {
185         return mType;
186     }
187 
188     @Override
getDisplayNumber()189     public String getDisplayNumber() {
190         return mDisplayNumber;
191     }
192 
193     @Override
194     @Nullable
getDisplayName()195     public String getDisplayName() {
196         return mDisplayName;
197     }
198 
199     @Override
getDescription()200     public String getDescription() {
201         return mDescription;
202     }
203 
204     @Override
getVideoFormat()205     public String getVideoFormat() {
206         return mVideoFormat;
207     }
208 
209     @Override
isPassthrough()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
getDisplayText()219     public String getDisplayText() {
220         return TextUtils.isEmpty(mDisplayName)
221                 ? mDisplayNumber
222                 : mDisplayNumber + " " + mDisplayName;
223     }
224 
225     @Override
getAppLinkText()226     public String getAppLinkText() {
227         return mAppLinkText;
228     }
229 
230     @Override
getAppLinkColor()231     public int getAppLinkColor() {
232         return mAppLinkColor;
233     }
234 
235     @Override
getAppLinkIconUri()236     public String getAppLinkIconUri() {
237         return mAppLinkIconUri;
238     }
239 
240     @Override
getAppLinkPosterArtUri()241     public String getAppLinkPosterArtUri() {
242         return mAppLinkPosterArtUri;
243     }
244 
245     @Override
getAppLinkIntentUri()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
getLogoUri()252     public String getLogoUri() {
253         return mLogoUri;
254     }
255 
256     @Override
isRecordingProhibited()257     public boolean isRecordingProhibited() {
258         return mRecordingProhibited;
259     }
260 
261     /** Checks whether this channel is physical tuner channel or not. */
262     @Override
isPhysicalTunerChannel()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
equals(Object o)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
hashCode()281     public int hashCode() {
282         return Objects.hash(mId, mInputId, mIsPassthrough);
283     }
284 
285     @Override
isBrowsable()286     public boolean isBrowsable() {
287         return mBrowsable;
288     }
289 
290     /** Checks whether this channel is searchable or not. */
291     @Override
isSearchable()292     public boolean isSearchable() {
293         return mSearchable;
294     }
295 
296     @Override
isLocked()297     public boolean isLocked() {
298         return mLocked;
299     }
300 
setBrowsable(boolean browsable)301     public void setBrowsable(boolean browsable) {
302         mBrowsable = browsable;
303     }
304 
setLocked(boolean locked)305     public void setLocked(boolean locked) {
306         mLocked = locked;
307     }
308 
309     /** Sets channel logo uri which is got from cloud. */
setLogoUri(String logoUri)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
hasSameReadOnlyInfo(Channel other)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
toString()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
copyFrom(Channel channel)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")
copyFrom(ChannelImpl channel)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. */
createPassthroughChannel(Uri uri)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}. */
createPassthroughChannel(String 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. */
isValid(Channel channel)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 
Builder()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 
Builder(Channel other)470         public Builder(Channel other) {
471             mChannel = new ChannelImpl();
472             mChannel.copyFrom(other);
473         }
474 
475         @VisibleForTesting
setId(long id)476         public Builder setId(long id) {
477             mChannel.mId = id;
478             return this;
479         }
480 
481         @VisibleForTesting
setPackageName(String packageName)482         public Builder setPackageName(String packageName) {
483             mChannel.mPackageName = packageName;
484             return this;
485         }
486 
setInputId(String inputId)487         public Builder setInputId(String inputId) {
488             mChannel.mInputId = inputId;
489             return this;
490         }
491 
setType(String type)492         public Builder setType(String type) {
493             mChannel.mType = type;
494             return this;
495         }
496 
497         @VisibleForTesting
setDisplayNumber(String displayNumber)498         public Builder setDisplayNumber(String displayNumber) {
499             mChannel.mDisplayNumber = normalizeDisplayNumber(displayNumber);
500             return this;
501         }
502 
503         @VisibleForTesting
setDisplayName(String displayName)504         public Builder setDisplayName(String displayName) {
505             mChannel.mDisplayName = displayName;
506             return this;
507         }
508 
509         @VisibleForTesting
setDescription(String description)510         public Builder setDescription(String description) {
511             mChannel.mDescription = description;
512             return this;
513         }
514 
setVideoFormat(String videoFormat)515         public Builder setVideoFormat(String videoFormat) {
516             mChannel.mVideoFormat = videoFormat;
517             return this;
518         }
519 
setBrowsable(boolean browsable)520         public Builder setBrowsable(boolean browsable) {
521             mChannel.mBrowsable = browsable;
522             return this;
523         }
524 
setSearchable(boolean searchable)525         public Builder setSearchable(boolean searchable) {
526             mChannel.mSearchable = searchable;
527             return this;
528         }
529 
setLocked(boolean locked)530         public Builder setLocked(boolean locked) {
531             mChannel.mLocked = locked;
532             return this;
533         }
534 
setPassthrough(boolean isPassthrough)535         public Builder setPassthrough(boolean isPassthrough) {
536             mChannel.mIsPassthrough = isPassthrough;
537             return this;
538         }
539 
540         @VisibleForTesting
setAppLinkText(String appLinkText)541         public Builder setAppLinkText(String appLinkText) {
542             mChannel.mAppLinkText = appLinkText;
543             return this;
544         }
545 
setAppLinkColor(int appLinkColor)546         public Builder setAppLinkColor(int appLinkColor) {
547             mChannel.mAppLinkColor = appLinkColor;
548             return this;
549         }
550 
setAppLinkIconUri(String appLinkIconUri)551         public Builder setAppLinkIconUri(String appLinkIconUri) {
552             mChannel.mAppLinkIconUri = appLinkIconUri;
553             return this;
554         }
555 
setAppLinkPosterArtUri(String appLinkPosterArtUri)556         public Builder setAppLinkPosterArtUri(String appLinkPosterArtUri) {
557             mChannel.mAppLinkPosterArtUri = appLinkPosterArtUri;
558             return this;
559         }
560 
561         @VisibleForTesting
setAppLinkIntentUri(String appLinkIntentUri)562         public Builder setAppLinkIntentUri(String appLinkIntentUri) {
563             mChannel.mAppLinkIntentUri = appLinkIntentUri;
564             return this;
565         }
566 
setRecordingProhibited(boolean recordingProhibited)567         public Builder setRecordingProhibited(boolean recordingProhibited) {
568             mChannel.mRecordingProhibited = recordingProhibited;
569             return this;
570         }
571 
build()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. */
prefetchImage(Context context, int type, int maxWidth, int maxHeight)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
loadBitmap( Context context, final int type, int maxWidth, int maxHeight, ImageLoader.ImageLoaderCallback callback)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
setChannelLogoExist(boolean exist)617     public void setChannelLogoExist(boolean exist) {
618         mChannelLogoExist = exist;
619     }
620 
621     /** Returns if channel logo exists. */
channelLogoExists()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      */
getAppLinkType(Context context)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      */
getAppLinkIntent(Context context)644     public Intent getAppLinkIntent(Context context) {
645         if (mAppLinkType == APP_LINK_TYPE_NOT_SET) {
646             initAppLinkTypeAndIntent(context);
647         }
648         return mAppLinkIntent;
649     }
650 
initAppLinkTypeAndIntent(Context context)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 
getImageUriString(int type)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 
DefaultComparator(Context context, TvInputManagerHelper inputManager)712         public DefaultComparator(Context context, TvInputManagerHelper inputManager) {
713             mContext = context;
714             mInputManager = inputManager;
715         }
716 
setDetectDuplicatesEnabled(boolean detectDuplicatesEnabled)717         public void setDetectDuplicatesEnabled(boolean detectDuplicatesEnabled) {
718             mDetectDuplicatesEnabled = detectDuplicatesEnabled;
719         }
720 
721         @SuppressWarnings("ReferenceEquality")
722         @Override
compare(Channel lhs, Channel rhs)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
getInputLabelForChannel(Channel channel)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