Home | History | Annotate | Download | only in util
      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.media.tv.TvInputInfo;
     22 import android.media.tv.TvInputManager;
     23 import android.media.tv.TvInputManager.TvInputCallback;
     24 import android.os.Handler;
     25 import android.support.annotation.VisibleForTesting;
     26 import android.text.TextUtils;
     27 import android.util.Log;
     28 
     29 import com.android.tv.common.SoftPreconditions;
     30 import com.android.tv.parental.ContentRatingsManager;
     31 import com.android.tv.parental.ParentalControlSettings;
     32 
     33 import java.util.ArrayList;
     34 import java.util.Collections;
     35 import java.util.Comparator;
     36 import java.util.HashMap;
     37 import java.util.HashSet;
     38 import java.util.List;
     39 import java.util.Map;
     40 import java.util.Set;
     41 
     42 public class TvInputManagerHelper {
     43     private static final String TAG = "TvInputManagerHelper";
     44     private static final boolean DEBUG = false;
     45 
     46     // Hardcoded list for known bundled inputs not written by OEM/SOCs.
     47     // Bundled (system) inputs not in the list will get the high priority
     48     // so they and their channels come first in the UI.
     49     private static final Set<String> BUNDLED_PACKAGE_SET = new HashSet<>();
     50 
     51     static {
     52         BUNDLED_PACKAGE_SET.add("com.android.tv");
     53         BUNDLED_PACKAGE_SET.add("com.android.usbtuner");
     54     }
     55 
     56     private final Context mContext;
     57     private final TvInputManager mTvInputManager;
     58     private final Map<String, Integer> mInputStateMap = new HashMap<>();
     59     private final Map<String, TvInputInfo> mInputMap = new HashMap<>();
     60     private final Map<String, Boolean> mInputIdToPartnerInputMap = new HashMap<>();
     61     private final TvInputCallback mInternalCallback = new TvInputCallback() {
     62         @Override
     63         public void onInputStateChanged(String inputId, int state) {
     64             if (DEBUG) Log.d(TAG, "onInputStateChanged " + inputId + " state=" + state);
     65             mInputStateMap.put(inputId, state);
     66             for (TvInputCallback callback : mCallbacks) {
     67                 callback.onInputStateChanged(inputId, state);
     68             }
     69         }
     70 
     71         @Override
     72         public void onInputAdded(String inputId) {
     73             if (DEBUG) Log.d(TAG, "onInputAdded " + inputId);
     74             TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
     75             if (info != null) {
     76                 mInputMap.put(inputId, info);
     77                 mInputStateMap.put(inputId, mTvInputManager.getInputState(inputId));
     78                 mInputIdToPartnerInputMap.put(inputId, isPartnerInput(info));
     79             }
     80             mContentRatingsManager.update();
     81             for (TvInputCallback callback : mCallbacks) {
     82                 callback.onInputAdded(inputId);
     83             }
     84         }
     85 
     86         @Override
     87         public void onInputRemoved(String inputId) {
     88             if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId);
     89             mInputMap.remove(inputId);
     90             mInputStateMap.remove(inputId);
     91             mInputIdToPartnerInputMap.remove(inputId);
     92             mContentRatingsManager.update();
     93             for (TvInputCallback callback : mCallbacks) {
     94                 callback.onInputRemoved(inputId);
     95             }
     96         }
     97 
     98         @Override
     99         public void onInputUpdated(String inputId) {
    100             if (DEBUG) Log.d(TAG, "onInputUpdated " + inputId);
    101             TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
    102             mInputMap.put(inputId, info);
    103             for (TvInputCallback callback : mCallbacks) {
    104                 callback.onInputUpdated(inputId);
    105             }
    106         }
    107     };
    108 
    109     private final Handler mHandler = new Handler();
    110     private boolean mStarted;
    111     private final HashSet<TvInputCallback> mCallbacks = new HashSet<>();
    112     private final ContentRatingsManager mContentRatingsManager;
    113     private final ParentalControlSettings mParentalControlSettings;
    114     private final Comparator<TvInputInfo> mTvInputInfoComparator;
    115 
    116     public TvInputManagerHelper(Context context) {
    117         mContext = context.getApplicationContext();
    118         mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
    119         mContentRatingsManager = new ContentRatingsManager(context);
    120         mParentalControlSettings = new ParentalControlSettings(context);
    121         mTvInputInfoComparator = new TvInputInfoComparator(this);
    122     }
    123 
    124     public void start() {
    125         if (mStarted) {
    126             return;
    127         }
    128         if (DEBUG) Log.d(TAG, "start");
    129         mStarted = true;
    130         mTvInputManager.registerCallback(mInternalCallback, mHandler);
    131         mInputMap.clear();
    132         mInputStateMap.clear();
    133         mInputIdToPartnerInputMap.clear();
    134         for (TvInputInfo input : mTvInputManager.getTvInputList()) {
    135             if (DEBUG) Log.d(TAG, "Input detected " + input);
    136             String inputId = input.getId();
    137             mInputMap.put(inputId, input);
    138             int state = mTvInputManager.getInputState(inputId);
    139             mInputStateMap.put(inputId, state);
    140             mInputIdToPartnerInputMap.put(inputId, isPartnerInput(input));
    141         }
    142         SoftPreconditions.checkState(mInputStateMap.size() == mInputMap.size(), TAG,
    143                 "mInputStateMap not the same size as mInputMap");
    144         mContentRatingsManager.update();
    145     }
    146 
    147     public void stop() {
    148         if (!mStarted) {
    149             return;
    150         }
    151         mTvInputManager.unregisterCallback(mInternalCallback);
    152         mStarted = false;
    153         mInputStateMap.clear();
    154         mInputMap.clear();
    155         mInputIdToPartnerInputMap.clear();
    156     }
    157 
    158     public List<TvInputInfo> getTvInputInfos(boolean availableOnly, boolean tunerOnly) {
    159         ArrayList<TvInputInfo> list = new ArrayList<>();
    160         for (Map.Entry<String, Integer> pair : mInputStateMap.entrySet()) {
    161             if (availableOnly && pair.getValue() == TvInputManager.INPUT_STATE_DISCONNECTED) {
    162                 continue;
    163             }
    164             TvInputInfo input = getTvInputInfo(pair.getKey());
    165             if (tunerOnly && input.getType() != TvInputInfo.TYPE_TUNER) {
    166                 continue;
    167             }
    168             list.add(input);
    169         }
    170         Collections.sort(list, mTvInputInfoComparator);
    171         return list;
    172     }
    173 
    174     /**
    175      * Returns the default comparator for {@link TvInputInfo}.
    176      * See {@link TvInputInfoComparator} for detail.
    177      */
    178     public Comparator<TvInputInfo> getDefaultTvInputInfoComparator() {
    179         return mTvInputInfoComparator;
    180     }
    181 
    182     /**
    183      * Checks if the input is from a partner.
    184      *
    185      * It's visible for comparator test.
    186      * Package private is enough for this method, but public is necessary to workaround mockito
    187      * bug.
    188      */
    189     @VisibleForTesting
    190     public boolean isPartnerInput(TvInputInfo inputInfo) {
    191         return isSystemInput(inputInfo) && !isBundledInput(inputInfo);
    192     }
    193 
    194     /**
    195      * Does the input have {@link ApplicationInfo#FLAG_SYSTEM} set.
    196      */
    197     public boolean isSystemInput(TvInputInfo inputInfo) {
    198         return inputInfo != null
    199                 && (inputInfo.getServiceInfo().applicationInfo.flags
    200                     & ApplicationInfo.FLAG_SYSTEM) != 0;
    201     }
    202 
    203     /**
    204      * Is the input one known bundled inputs not written by OEM/SOCs.
    205      */
    206     public boolean isBundledInput(TvInputInfo inputInfo) {
    207         return inputInfo != null
    208                && BUNDLED_PACKAGE_SET.contains(
    209                    inputInfo.getServiceInfo().applicationInfo.packageName);
    210     }
    211 
    212     /**
    213      * Returns if the given input is bundled and written by OEM/SOCs.
    214      * This returns the cached result.
    215      */
    216     public boolean isPartnerInput(String inputId) {
    217         Boolean isPartnerInput = mInputIdToPartnerInputMap.get(inputId);
    218         return (isPartnerInput != null) ? isPartnerInput : false;
    219     }
    220 
    221     /**
    222      * Loads label of {@code info}.
    223      *
    224      * It's visible for comparator test to mock TvInputInfo.
    225      * Package private is enough for this method, but public is necessary to workaround mockito
    226      * bug.
    227      */
    228     @VisibleForTesting
    229     public String loadLabel(TvInputInfo info) {
    230         return info.loadLabel(mContext).toString();
    231     }
    232 
    233     /**
    234      * Returns if TV input exists with the input id.
    235      */
    236     public boolean hasTvInputInfo(String inputId) {
    237         SoftPreconditions.checkState(mStarted, TAG,
    238                 "hasTvInputInfo() called before TvInputManagerHelper was started.");
    239         if (!mStarted) {
    240             return false;
    241         }
    242         return !TextUtils.isEmpty(inputId) && mInputMap.get(inputId) != null;
    243     }
    244 
    245     public TvInputInfo getTvInputInfo(String inputId) {
    246         SoftPreconditions.checkState(mStarted, TAG,
    247                 "getTvInputInfo() called before TvInputManagerHelper was started.");
    248         if (!mStarted) {
    249             return null;
    250         }
    251         if (inputId == null) {
    252             return null;
    253         }
    254         return mInputMap.get(inputId);
    255     }
    256 
    257     public ApplicationInfo getTvInputAppInfo(String inputId) {
    258         TvInputInfo info = getTvInputInfo(inputId);
    259         return info == null ? null : info.getServiceInfo().applicationInfo;
    260     }
    261 
    262     public int getTunerTvInputSize() {
    263         int size = 0;
    264         for (TvInputInfo input : mInputMap.values()) {
    265             if (input.getType() == TvInputInfo.TYPE_TUNER) {
    266                 ++size;
    267             }
    268         }
    269         return size;
    270     }
    271 
    272     public int getInputState(TvInputInfo inputInfo) {
    273         return getInputState(inputInfo.getId());
    274     }
    275 
    276     public int getInputState(String inputId) {
    277         SoftPreconditions.checkState(mStarted, TAG, "AvailabilityManager not started");
    278         if (!mStarted) {
    279             return TvInputManager.INPUT_STATE_DISCONNECTED;
    280 
    281         }
    282         Integer state = mInputStateMap.get(inputId);
    283         if (state == null) {
    284             Log.w(TAG, "getInputState: no such input (id=" + inputId + ")");
    285             return TvInputManager.INPUT_STATE_DISCONNECTED;
    286         }
    287         return state;
    288     }
    289 
    290     public void addCallback(TvInputCallback callback) {
    291         mCallbacks.add(callback);
    292     }
    293 
    294     public void removeCallback(TvInputCallback callback) {
    295         mCallbacks.remove(callback);
    296     }
    297 
    298     public ParentalControlSettings getParentalControlSettings() {
    299         return mParentalControlSettings;
    300     }
    301 
    302     /**
    303      * Returns a ContentRatingsManager instance for a given application context.
    304      */
    305     public ContentRatingsManager getContentRatingsManager() {
    306         return mContentRatingsManager;
    307     }
    308 
    309     /**
    310      * Default comparator for TvInputInfo.
    311      *
    312      * It's static class that accepts {@link TvInputManagerHelper} as parameter to test.
    313      * To test comparator, we need to mock API in parent class such as {@link #isPartnerInput},
    314      * but it's impossible for an inner class to use mocked methods.
    315      * (i.e. Mockito's spy doesn't work)
    316      */
    317     @VisibleForTesting
    318     static class TvInputInfoComparator implements Comparator<TvInputInfo> {
    319         private final TvInputManagerHelper mInputManager;
    320 
    321         public TvInputInfoComparator(TvInputManagerHelper inputManager) {
    322             mInputManager = inputManager;
    323         }
    324 
    325         @Override
    326         public int compare(TvInputInfo lhs, TvInputInfo rhs) {
    327             if (mInputManager.isPartnerInput(lhs) != mInputManager.isPartnerInput(rhs)) {
    328                 return mInputManager.isPartnerInput(lhs) ? -1 : 1;
    329             }
    330             return mInputManager.loadLabel(lhs).compareTo(mInputManager.loadLabel(rhs));
    331         }
    332     }
    333 }
    334