• 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.util;
18 
19 import android.content.Context;
20 import android.content.pm.ApplicationInfo;
21 import android.content.pm.PackageManager;
22 import android.graphics.drawable.Drawable;
23 import android.hardware.hdmi.HdmiDeviceInfo;
24 import android.media.tv.TvInputInfo;
25 import android.media.tv.TvInputManager;
26 import android.media.tv.TvInputManager.TvInputCallback;
27 import android.os.Handler;
28 import android.support.annotation.VisibleForTesting;
29 import android.text.TextUtils;
30 import android.util.ArrayMap;
31 import android.util.Log;
32 
33 import com.android.tv.Features;
34 import com.android.tv.common.SoftPreconditions;
35 import com.android.tv.common.TvCommonUtils;
36 import com.android.tv.parental.ContentRatingsManager;
37 import com.android.tv.parental.ParentalControlSettings;
38 
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Collections;
42 import java.util.Comparator;
43 import java.util.HashMap;
44 import java.util.HashSet;
45 import java.util.List;
46 import java.util.Map;
47 
48 public class TvInputManagerHelper {
49     private static final String TAG = "TvInputManagerHelper";
50     private static final boolean DEBUG = false;
51 
52     /**
53      * Types of HDMI device and bundled tuner.
54      */
55     public static final int TYPE_CEC_DEVICE = -2;
56     public static final int TYPE_BUNDLED_TUNER = -3;
57     public static final int TYPE_CEC_DEVICE_RECORDER = -4;
58     public static final int TYPE_CEC_DEVICE_PLAYBACK = -5;
59     public static final int TYPE_MHL_MOBILE = -6;
60 
61     private static final String PERMISSION_ACCESS_ALL_EPG_DATA =
62             "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA";
63     private static final String [] mPhysicalTunerBlackList = {
64     };
65     private static final String META_LABEL_SORT_KEY = "input_sort_key";
66 
67     /**
68      * The default tv input priority to show.
69      */
70     private static final ArrayList<Integer> DEFAULT_TV_INPUT_PRIORITY = new ArrayList<>();
71     static {
72         DEFAULT_TV_INPUT_PRIORITY.add(TYPE_BUNDLED_TUNER);
73         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_TUNER);
74         DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE);
75         DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE_RECORDER);
76         DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE_PLAYBACK);
77         DEFAULT_TV_INPUT_PRIORITY.add(TYPE_MHL_MOBILE);
78         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_HDMI);
79         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_DVI);
80         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_COMPONENT);
81         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_SVIDEO);
82         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_COMPOSITE);
83         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_DISPLAY_PORT);
84         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_VGA);
85         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_SCART);
86         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_OTHER);
87     }
88 
89     private static final String[] PARTNER_TUNER_INPUT_PREFIX_BLACKLIST = {
90     };
91 
92     private static final String[] TESTABLE_INPUTS = {
93             "com.android.tv.testinput/.TestTvInputService"
94     };
95 
96     private final Context mContext;
97     private final PackageManager mPackageManager;
98     private final TvInputManager mTvInputManager;
99     private final Map<String, Integer> mInputStateMap = new HashMap<>();
100     private final Map<String, TvInputInfo> mInputMap = new HashMap<>();
101     private final Map<String, String> mTvInputLabels = new ArrayMap<>();
102     private final Map<String, String> mTvInputCustomLabels = new ArrayMap<>();
103     private final Map<String, Boolean> mInputIdToPartnerInputMap = new HashMap<>();
104 
105     private final Map<String, CharSequence> mTvInputApplicationLabels = new ArrayMap<>();
106     private final Map<String, Drawable> mTvInputApplicationIcons = new ArrayMap<>();
107     private final Map<String, Drawable> mTvInputAppliactionBanners = new ArrayMap<>();
108 
109     private final TvInputCallback mInternalCallback = new TvInputCallback() {
110         @Override
111         public void onInputStateChanged(String inputId, int state) {
112             if (DEBUG) Log.d(TAG, "onInputStateChanged " + inputId + " state=" + state);
113             if (isInBlackList(inputId)) {
114                 return;
115             }
116             mInputStateMap.put(inputId, state);
117             for (TvInputCallback callback : mCallbacks) {
118                 callback.onInputStateChanged(inputId, state);
119             }
120         }
121 
122         @Override
123         public void onInputAdded(String inputId) {
124             if (DEBUG) Log.d(TAG, "onInputAdded " + inputId);
125             if (isInBlackList(inputId)) {
126                 return;
127             }
128             TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
129             if (info != null) {
130                 mInputMap.put(inputId, info);
131                 mTvInputLabels.put(inputId, info.loadLabel(mContext).toString());
132                 CharSequence inputCustomLabel = info.loadCustomLabel(mContext);
133                 if (inputCustomLabel != null) {
134                     mTvInputCustomLabels.put(inputId, inputCustomLabel.toString());
135                 }
136                 mInputStateMap.put(inputId, mTvInputManager.getInputState(inputId));
137                 mInputIdToPartnerInputMap.put(inputId, isPartnerInput(info));
138             }
139             mContentRatingsManager.update();
140             for (TvInputCallback callback : mCallbacks) {
141                 callback.onInputAdded(inputId);
142             }
143         }
144 
145         @Override
146         public void onInputRemoved(String inputId) {
147             if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId);
148             mInputMap.remove(inputId);
149             mTvInputLabels.remove(inputId);
150             mTvInputCustomLabels.remove(inputId);
151             mTvInputApplicationLabels.remove(inputId);
152             mTvInputApplicationIcons.remove(inputId);
153             mTvInputAppliactionBanners.remove(inputId);
154             mInputStateMap.remove(inputId);
155             mInputIdToPartnerInputMap.remove(inputId);
156             mContentRatingsManager.update();
157             for (TvInputCallback callback : mCallbacks) {
158                 callback.onInputRemoved(inputId);
159             }
160             ImageCache.getInstance().remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(
161                     inputId));
162         }
163 
164         @Override
165         public void onInputUpdated(String inputId) {
166             if (DEBUG) Log.d(TAG, "onInputUpdated " + inputId);
167             if (isInBlackList(inputId)) {
168                 return;
169             }
170             TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
171             mInputMap.put(inputId, info);
172             mTvInputLabels.put(inputId, info.loadLabel(mContext).toString());
173             CharSequence inputCustomLabel = info.loadCustomLabel(mContext);
174             if (inputCustomLabel != null) {
175                 mTvInputCustomLabels.put(inputId, inputCustomLabel.toString());
176             }
177             mTvInputApplicationLabels.remove(inputId);
178             mTvInputApplicationIcons.remove(inputId);
179             mTvInputAppliactionBanners.remove(inputId);
180             for (TvInputCallback callback : mCallbacks) {
181                 callback.onInputUpdated(inputId);
182             }
183             ImageCache.getInstance().remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(
184                     inputId));
185         }
186 
187         @Override
188         public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
189             if (DEBUG) Log.d(TAG, "onTvInputInfoUpdated " + inputInfo);
190             mInputMap.put(inputInfo.getId(), inputInfo);
191             mTvInputLabels.put(inputInfo.getId(), inputInfo.loadLabel(mContext).toString());
192             CharSequence inputCustomLabel = inputInfo.loadCustomLabel(mContext);
193             if (inputCustomLabel != null) {
194                 mTvInputCustomLabels.put(inputInfo.getId(), inputCustomLabel.toString());
195             }
196             for (TvInputCallback callback : mCallbacks) {
197                 callback.onTvInputInfoUpdated(inputInfo);
198             }
199             ImageCache.getInstance().remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(
200                     inputInfo.getId()));
201         }
202     };
203 
204     private final Handler mHandler = new Handler();
205     private boolean mStarted;
206     private final HashSet<TvInputCallback> mCallbacks = new HashSet<>();
207     private final ContentRatingsManager mContentRatingsManager;
208     private final ParentalControlSettings mParentalControlSettings;
209     private final Comparator<TvInputInfo> mTvInputInfoComparator;
210 
TvInputManagerHelper(Context context)211     public TvInputManagerHelper(Context context) {
212         mContext = context.getApplicationContext();
213         mPackageManager = context.getPackageManager();
214         mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
215         mContentRatingsManager = new ContentRatingsManager(context);
216         mParentalControlSettings = new ParentalControlSettings(context);
217         mTvInputInfoComparator = new InputComparatorInternal(this);
218     }
219 
start()220     public void start() {
221         if (!hasTvInputManager()) {
222             // Not a TV device
223             return;
224         }
225         if (mStarted) {
226             return;
227         }
228         if (DEBUG) Log.d(TAG, "start");
229         mStarted = true;
230         mTvInputManager.registerCallback(mInternalCallback, mHandler);
231         mInputMap.clear();
232         mTvInputLabels.clear();
233         mTvInputCustomLabels.clear();
234         mTvInputApplicationLabels.clear();
235         mTvInputApplicationIcons.clear();
236         mTvInputAppliactionBanners.clear();
237         mInputStateMap.clear();
238         mInputIdToPartnerInputMap.clear();
239         for (TvInputInfo input : mTvInputManager.getTvInputList()) {
240             if (DEBUG) Log.d(TAG, "Input detected " + input);
241             String inputId = input.getId();
242             if (isInBlackList(inputId)) {
243                 continue;
244             }
245             mInputMap.put(inputId, input);
246             int state = mTvInputManager.getInputState(inputId);
247             mInputStateMap.put(inputId, state);
248             mInputIdToPartnerInputMap.put(inputId, isPartnerInput(input));
249         }
250         SoftPreconditions.checkState(mInputStateMap.size() == mInputMap.size(), TAG,
251                 "mInputStateMap not the same size as mInputMap");
252         mContentRatingsManager.update();
253     }
254 
stop()255     public void stop() {
256         if (!mStarted) {
257             return;
258         }
259         mTvInputManager.unregisterCallback(mInternalCallback);
260         mStarted = false;
261         mInputStateMap.clear();
262         mInputMap.clear();
263         mTvInputLabels.clear();
264         mTvInputCustomLabels.clear();
265         mTvInputApplicationLabels.clear();
266         mTvInputApplicationIcons.clear();
267         mTvInputAppliactionBanners.clear();;
268         mInputIdToPartnerInputMap.clear();
269     }
270 
271     /**
272      * Clears the TvInput labels map.
273      */
clearTvInputLabels()274     public void clearTvInputLabels() {
275         mTvInputLabels.clear();
276         mTvInputCustomLabels.clear();
277         mTvInputApplicationLabels.clear();
278     }
279 
getTvInputInfos(boolean availableOnly, boolean tunerOnly)280     public List<TvInputInfo> getTvInputInfos(boolean availableOnly, boolean tunerOnly) {
281         ArrayList<TvInputInfo> list = new ArrayList<>();
282         for (Map.Entry<String, Integer> pair : mInputStateMap.entrySet()) {
283             if (availableOnly && pair.getValue() == TvInputManager.INPUT_STATE_DISCONNECTED) {
284                 continue;
285             }
286             TvInputInfo input = getTvInputInfo(pair.getKey());
287             if (tunerOnly && input.getType() != TvInputInfo.TYPE_TUNER) {
288                 continue;
289             }
290             list.add(input);
291         }
292         Collections.sort(list, mTvInputInfoComparator);
293         return list;
294     }
295 
296     /**
297      * Returns the default comparator for {@link TvInputInfo}.
298      * See {@link InputComparatorInternal} for detail.
299      */
getDefaultTvInputInfoComparator()300     public Comparator<TvInputInfo> getDefaultTvInputInfoComparator() {
301         return mTvInputInfoComparator;
302     }
303 
304     /**
305      * Checks if the input is from a partner.
306      *
307      * It's visible for comparator test.
308      * Package private is enough for this method, but public is necessary to workaround mockito
309      * bug.
310      */
311     @VisibleForTesting
isPartnerInput(TvInputInfo inputInfo)312     public boolean isPartnerInput(TvInputInfo inputInfo) {
313         return isSystemInput(inputInfo) && !isBundledInput(inputInfo);
314     }
315 
316     /**
317      * Does the input have {@link ApplicationInfo#FLAG_SYSTEM} set.
318      */
isSystemInput(TvInputInfo inputInfo)319     public boolean isSystemInput(TvInputInfo inputInfo) {
320         return inputInfo != null
321                 && (inputInfo.getServiceInfo().applicationInfo.flags
322                     & ApplicationInfo.FLAG_SYSTEM) != 0;
323     }
324 
325     /**
326      * Is the input one known bundled inputs not written by OEM/SOCs.
327      */
isBundledInput(TvInputInfo inputInfo)328     public boolean isBundledInput(TvInputInfo inputInfo) {
329         return inputInfo != null && Utils.isInBundledPackageSet(inputInfo.getServiceInfo()
330                 .applicationInfo.packageName);
331     }
332 
333     /**
334      * Returns if the given input is bundled and written by OEM/SOCs.
335      * This returns the cached result.
336      */
isPartnerInput(String inputId)337     public boolean isPartnerInput(String inputId) {
338         Boolean isPartnerInput = mInputIdToPartnerInputMap.get(inputId);
339         return (isPartnerInput != null) ? isPartnerInput : false;
340     }
341 
342     /**
343      * Is (Context.TV_INPUT_SERVICE) available.
344      *
345      * <p>This is only available on TV devices.
346      */
hasTvInputManager()347     public boolean hasTvInputManager() {
348         return mTvInputManager != null;
349     }
350 
351     /**
352      * Loads label of {@code info}.
353      */
loadLabel(TvInputInfo info)354     public String loadLabel(TvInputInfo info) {
355         String label = mTvInputLabels.get(info.getId());
356         if (label == null) {
357             label = info.loadLabel(mContext).toString();
358             mTvInputLabels.put(info.getId(), label);
359         }
360         return label;
361     }
362 
363     /**
364      * Loads custom label of {@code info}
365      */
loadCustomLabel(TvInputInfo info)366     public String loadCustomLabel(TvInputInfo info) {
367         String customLabel = mTvInputCustomLabels.get(info.getId());
368         if (customLabel == null) {
369             CharSequence customLabelCharSequence = info.loadCustomLabel(mContext);
370             if (customLabelCharSequence != null) {
371                 customLabel = customLabelCharSequence.toString();
372                 mTvInputCustomLabels.put(info.getId(), customLabel);
373             }
374         }
375         return customLabel;
376     }
377 
378     /**
379      * Gets the tv input application's label.
380      */
getTvInputApplicationLabel(CharSequence inputId)381     public CharSequence getTvInputApplicationLabel(CharSequence inputId) {
382         return mTvInputApplicationLabels.get(inputId);
383     }
384 
385     /**
386      * Stores the tv input application's label.
387      */
setTvInputApplicationLabel(String inputId, CharSequence label)388     public void setTvInputApplicationLabel(String inputId, CharSequence label) {
389         mTvInputApplicationLabels.put(inputId, label);
390     }
391 
392     /**
393      * Gets the tv input application's icon.
394      */
getTvInputApplicationIcon(String inputId)395     public Drawable getTvInputApplicationIcon(String inputId) {
396         return mTvInputApplicationIcons.get(inputId);
397     }
398 
399     /**
400      * Stores the tv input application's icon.
401      */
setTvInputApplicationIcon(String inputId, Drawable icon)402     public void setTvInputApplicationIcon(String inputId, Drawable icon) {
403         mTvInputApplicationIcons.put(inputId, icon);
404     }
405 
406     /**
407      * Gets the tv input application's banner.
408      */
getTvInputApplicationBanner(String inputId)409     public Drawable getTvInputApplicationBanner(String inputId) {
410         return mTvInputAppliactionBanners.get(inputId);
411     }
412 
413     /**
414      * Stores the tv input application's banner.
415      */
setTvInputApplicationBanner(String inputId, Drawable banner)416     public void setTvInputApplicationBanner(String inputId, Drawable banner) {
417         mTvInputAppliactionBanners.put(inputId, banner);
418     }
419 
420     /**
421      * Returns if TV input exists with the input id.
422      */
hasTvInputInfo(String inputId)423     public boolean hasTvInputInfo(String inputId) {
424         SoftPreconditions.checkState(mStarted, TAG,
425                 "hasTvInputInfo() called before TvInputManagerHelper was started.");
426         return mStarted && !TextUtils.isEmpty(inputId) && mInputMap.get(inputId) != null;
427     }
428 
getTvInputInfo(String inputId)429     public TvInputInfo getTvInputInfo(String inputId) {
430         SoftPreconditions.checkState(mStarted, TAG,
431                 "getTvInputInfo() called before TvInputManagerHelper was started.");
432         if (!mStarted) {
433             return null;
434         }
435         if (inputId == null) {
436             return null;
437         }
438         return mInputMap.get(inputId);
439     }
440 
getTvInputAppInfo(String inputId)441     public ApplicationInfo getTvInputAppInfo(String inputId) {
442         TvInputInfo info = getTvInputInfo(inputId);
443         return info == null ? null : info.getServiceInfo().applicationInfo;
444     }
445 
getTunerTvInputSize()446     public int getTunerTvInputSize() {
447         int size = 0;
448         for (TvInputInfo input : mInputMap.values()) {
449             if (input.getType() == TvInputInfo.TYPE_TUNER) {
450                 ++size;
451             }
452         }
453         return size;
454     }
455 
getInputState(TvInputInfo inputInfo)456     public int getInputState(TvInputInfo inputInfo) {
457         return getInputState(inputInfo.getId());
458     }
459 
getInputState(String inputId)460     public int getInputState(String inputId) {
461         SoftPreconditions.checkState(mStarted, TAG, "AvailabilityManager not started");
462         if (!mStarted) {
463             return TvInputManager.INPUT_STATE_DISCONNECTED;
464 
465         }
466         Integer state = mInputStateMap.get(inputId);
467         if (state == null) {
468             Log.w(TAG, "getInputState: no such input (id=" + inputId + ")");
469             return TvInputManager.INPUT_STATE_DISCONNECTED;
470         }
471         return state;
472     }
473 
addCallback(TvInputCallback callback)474     public void addCallback(TvInputCallback callback) {
475         mCallbacks.add(callback);
476     }
477 
removeCallback(TvInputCallback callback)478     public void removeCallback(TvInputCallback callback) {
479         mCallbacks.remove(callback);
480     }
481 
getParentalControlSettings()482     public ParentalControlSettings getParentalControlSettings() {
483         return mParentalControlSettings;
484     }
485 
486     /**
487      * Returns a ContentRatingsManager instance for a given application context.
488      */
getContentRatingsManager()489     public ContentRatingsManager getContentRatingsManager() {
490         return mContentRatingsManager;
491     }
492 
getInputSortKey(TvInputInfo input)493     private int getInputSortKey(TvInputInfo input) {
494         return input.getServiceInfo().metaData.getInt(META_LABEL_SORT_KEY,
495                 Integer.MAX_VALUE);
496     }
497 
isInputPhysicalTuner(TvInputInfo input)498     private boolean isInputPhysicalTuner(TvInputInfo input) {
499         String packageName = input.getServiceInfo().packageName;
500         if (Arrays.asList(mPhysicalTunerBlackList).contains(packageName)) {
501             return false;
502         }
503 
504         if (input.createSetupIntent() == null) {
505             return false;
506         } else {
507             boolean mayBeTunerInput = mPackageManager.checkPermission(
508                     PERMISSION_ACCESS_ALL_EPG_DATA, input.getServiceInfo().packageName)
509                     == PackageManager.PERMISSION_GRANTED;
510             if (!mayBeTunerInput) {
511                 try {
512                     ApplicationInfo ai = mPackageManager.getApplicationInfo(
513                             input.getServiceInfo().packageName, 0);
514                     if ((ai.flags & (ApplicationInfo.FLAG_SYSTEM
515                             | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) == 0) {
516                         return false;
517                     }
518                 } catch (PackageManager.NameNotFoundException e) {
519                     return false;
520                 }
521             }
522         }
523         return true;
524     }
525 
isInBlackList(String inputId)526     private boolean isInBlackList(String inputId) {
527         if (Features.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) {
528             for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLACKLIST) {
529                 if (inputId.contains(disabledTunerInputPrefix)) {
530                     return true;
531                 }
532             }
533         }
534         if (TvCommonUtils.isRunningInTest()) {
535             for (String testableInput : TESTABLE_INPUTS) {
536                 if (testableInput.equals(inputId)) {
537                     return false;
538                 }
539             }
540             return true;
541         }
542         return false;
543     }
544 
545     /**
546      * Default comparator for TvInputInfo.
547      *
548      * It's static class that accepts {@link TvInputManagerHelper} as parameter to test.
549      * To test comparator, we need to mock API in parent class such as {@link #isPartnerInput},
550      * but it's impossible for an inner class to use mocked methods.
551      * (i.e. Mockito's spy doesn't work)
552      */
553     @VisibleForTesting
554     static class InputComparatorInternal implements Comparator<TvInputInfo> {
555         private final TvInputManagerHelper mInputManager;
556 
InputComparatorInternal(TvInputManagerHelper inputManager)557         public InputComparatorInternal(TvInputManagerHelper inputManager) {
558             mInputManager = inputManager;
559         }
560 
561         @Override
compare(TvInputInfo lhs, TvInputInfo rhs)562         public int compare(TvInputInfo lhs, TvInputInfo rhs) {
563             if (mInputManager.isPartnerInput(lhs) != mInputManager.isPartnerInput(rhs)) {
564                 return mInputManager.isPartnerInput(lhs) ? -1 : 1;
565             }
566             return mInputManager.loadLabel(lhs).compareTo(mInputManager.loadLabel(rhs));
567         }
568     }
569 
570     /**
571      * A comparator used for {@link com.android.tv.ui.SelectInputView} to show the list of
572      * TV inputs.
573      */
574     public static class HardwareInputComparator implements Comparator<TvInputInfo> {
575         private Map<Integer, Integer> mTypePriorities = new HashMap<>();
576         private final TvInputManagerHelper mTvInputManagerHelper;
577         private final Context mContext;
578 
HardwareInputComparator(Context context, TvInputManagerHelper tvInputManagerHelper)579         public HardwareInputComparator(Context context, TvInputManagerHelper tvInputManagerHelper) {
580             mContext = context;
581             mTvInputManagerHelper = tvInputManagerHelper;
582             setupDeviceTypePriorities();
583         }
584 
585         @Override
compare(TvInputInfo lhs, TvInputInfo rhs)586         public int compare(TvInputInfo lhs, TvInputInfo rhs) {
587             if (lhs == null) {
588                 return (rhs == null) ? 0 : 1;
589             }
590             if (rhs == null) {
591                 return -1;
592             }
593 
594             boolean enabledL = (mTvInputManagerHelper.getInputState(lhs)
595                     != TvInputManager.INPUT_STATE_DISCONNECTED);
596             boolean enabledR = (mTvInputManagerHelper.getInputState(rhs)
597                     != TvInputManager.INPUT_STATE_DISCONNECTED);
598             if (enabledL != enabledR) {
599                 return enabledL ? -1 : 1;
600             }
601 
602             int priorityL = getPriority(lhs);
603             int priorityR = getPriority(rhs);
604             if (priorityL != priorityR) {
605                 return priorityL - priorityR;
606             }
607 
608             if (lhs.getType() == TvInputInfo.TYPE_TUNER
609                     && rhs.getType() == TvInputInfo.TYPE_TUNER) {
610                 boolean isPhysicalL = mTvInputManagerHelper.isInputPhysicalTuner(lhs);
611                 boolean isPhysicalR = mTvInputManagerHelper.isInputPhysicalTuner(rhs);
612                 if (isPhysicalL != isPhysicalR) {
613                     return isPhysicalL ? -1 : 1;
614                 }
615             }
616 
617             int sortKeyL = mTvInputManagerHelper.getInputSortKey(lhs);
618             int sortKeyR = mTvInputManagerHelper.getInputSortKey(rhs);
619             if (sortKeyL != sortKeyR) {
620                 return sortKeyR - sortKeyL;
621             }
622 
623             String parentLabelL = lhs.getParentId() != null
624                     ? getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getParentId()))
625                             : getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getId()));
626             String parentLabelR = rhs.getParentId() != null
627                     ? getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getParentId()))
628                             : getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getId()));
629 
630             if (!TextUtils.equals(parentLabelL, parentLabelR)) {
631                 return parentLabelL.compareToIgnoreCase(parentLabelR);
632             }
633             return getLabel(lhs).compareToIgnoreCase(getLabel(rhs));
634         }
635 
getLabel(TvInputInfo input)636         private String getLabel(TvInputInfo input) {
637             if (input == null) {
638                 return "";
639             }
640             String label = mTvInputManagerHelper.loadCustomLabel(input);
641             if (TextUtils.isEmpty(label)) {
642                 label = mTvInputManagerHelper.loadLabel(input);
643             }
644             return label;
645         }
646 
getPriority(TvInputInfo info)647         private int getPriority(TvInputInfo info) {
648             Integer priority = null;
649             if (mTypePriorities != null) {
650                 priority = mTypePriorities.get(getTvInputTypeForPriority(info));
651             }
652             if (priority != null) {
653                 return priority;
654             }
655             return Integer.MAX_VALUE;
656         }
657 
setupDeviceTypePriorities()658         private void setupDeviceTypePriorities() {
659             mTypePriorities = Partner.getInstance(mContext).getInputsOrderMap();
660 
661             // Fill in any missing priorities in the map we got from the OEM
662             int priority = mTypePriorities.size();
663             for (int type : DEFAULT_TV_INPUT_PRIORITY) {
664                 if (!mTypePriorities.containsKey(type)) {
665                     mTypePriorities.put(type, priority++);
666                 }
667             }
668         }
669 
getTvInputTypeForPriority(TvInputInfo info)670         private int getTvInputTypeForPriority(TvInputInfo info) {
671             if (info.getHdmiDeviceInfo() != null) {
672                 if (info.getHdmiDeviceInfo().isCecDevice()) {
673                     switch (info.getHdmiDeviceInfo().getDeviceType()) {
674                         case HdmiDeviceInfo.DEVICE_RECORDER:
675                             return TYPE_CEC_DEVICE_RECORDER;
676                         case HdmiDeviceInfo.DEVICE_PLAYBACK:
677                             return TYPE_CEC_DEVICE_PLAYBACK;
678                         default:
679                             return TYPE_CEC_DEVICE;
680                     }
681                 } else if (info.getHdmiDeviceInfo().isMhlDevice()) {
682                     return TYPE_MHL_MOBILE;
683                 }
684             }
685             return info.getType();
686         }
687     }
688 }
689