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