TvInputManagerHelper.java revision 919e1ed7e914029a1a0054237d86dc7b19ced898
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 17package com.android.tv.util; 18 19import android.content.Context; 20import android.content.pm.ApplicationInfo; 21import android.media.tv.TvInputInfo; 22import android.media.tv.TvInputManager; 23import android.media.tv.TvInputManager.TvInputCallback; 24import android.os.Handler; 25import android.support.annotation.VisibleForTesting; 26import android.text.TextUtils; 27import android.util.Log; 28 29import com.android.tv.Features; 30import com.android.tv.common.SoftPreconditions; 31import com.android.tv.parental.ContentRatingsManager; 32import com.android.tv.parental.ParentalControlSettings; 33 34import java.util.ArrayList; 35import java.util.Collections; 36import java.util.Comparator; 37import java.util.HashMap; 38import java.util.HashSet; 39import java.util.List; 40import java.util.Map; 41 42public 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