Android TV 輸入框架 (TIF) 簡化了向 Android TV 傳送直播內容的過程。Android TIF 爲製造商提供了一個標準 API,供他們建立可以控制 Android TV 的輸入模塊,並讓他們能夠經過 TV Input 發佈的元數據來搜索和推薦直播電視內容。java
LiveTv是Android TV系統中的一個TV應用,它是一個系統應用,Aosp中提供了一個參考的LiveTv,那麼什麼是LiveTv,它和普通的TV應用有什麼區別呢?簡單一句就是:它向用戶展現直播電視內容。可是LiveTv這個應用自己不提供這些直播的數據,它主要的功能是作展現。那麼這些內容哪裏來呢,答案是Tv Input.android
TvInput就是咱們上面LiveTv直播內容的數據來源,這些來源既能夠是硬件來源(例如 HDMI 端口和內置調諧器)中的直播視頻內容,也能夠是軟件來源(例如經過互聯網在線播放的內容)中的直播視頻內容,軟件來源通常是個獨立的apk,咱們把它叫Input應用。有了這些輸入來源,咱們就能夠在LiveTv中設置這些來源,而後將它們輸入的內容展現到LiveTv中。git
有了LiveTv用於展現內容,有了TvInput提供內容,是否是就萬事大吉了,事情並無那麼簡單。由於LiveTv和TvInput是不能直接交互的,就比如兩個說不一樣語言的人,彼此能看見對方,可是無法交流。這個時候Tv Input Framework出現了,到這裏應該有人就納悶了爲何LiveTv和TvInput不直接交互呢,須要TIF這個第三者。個人理解: TIF的做用是爲了統一接口方便交流,由於TvInput不少狀況下是由第三方實現的,而LiveTv是由廠商實現的,二者之間無法直接交互或者交互起來很麻煩,須要事先協商接口,因而Android 提供了一套TIF,說大家提供數據的和接受數據的都按這個標準來,因而這個問題就愉快的解決了。github
下面貼一張Android官方的TIF原理圖,很是的形象數據庫
圖中的TVProvider和TV Input Manager就是TIF中的內容。其中TV主要的功能是將頻道和節目信息從TVInput傳入到LiveTv.而TV Input Manager它對LiveTv與 TV Input 之間的交互進行控制,並提供家長控制功能。TV Input Manager 必須與 TV Input 建立一對一的會話。bash
前面 的概念解釋中已經說過,TvInput應用是直播內容的輸入來源,那該怎麼建立這個應用呢?其實主要就是建立一個咱們自定義的Service,而這個Service要繼承系統的TvInputService,固然爲了簡化這個過程咱們可使用android官方提供的TIF 隨播內容庫:markdown
compile 'com.google.android.libraries.tv:companionlibrary:0.2'
複製代碼
public class TvService extends BaseTvInputService { @Nullable @Override public TvInputService.Session onCreateSession(@NonNull String inputId) { TvInputSessionImpl session = new TvInputSessionImpl(this, inputId); session.setOverlayViewEnabled(true); return session; } } 複製代碼
這裏的BaseTvInputService也是繼承的TvInputService.而後咱們須要複寫onCreateSession方法,建立咱們本身的Session,用於和TvInputManager交互,最後在清單文件中配置以下:session
<service android:name=".service.TvService" android:permission="android.permission.BIND_TV_INPUT"> <intent-filter> <action android:name="android.media.tv.TvInputService" /> </intent-filter> <meta-data android:name="android.media.tv.input" android:resource="@xml/richtvinputservice" /> </service> 複製代碼
注意上面有3個地方的改動:app
添加權限:框架
android:permission="android.permission.BIND_TV_INPUT" 複製代碼
添加過濾器action
<intent-filter> <action android:name="android.media.tv.TvInputService" /> </intent-filter> 複製代碼
添加meta-data
<meta-data android:name="android.media.tv.input" android:resource="@xml/richtvinputservice" /> 複製代碼
<?xml version="1.0" encoding="utf-8"?> <tv-input xmlns:android="http://schemas.android.com/apk/res/android" android:settingsActivity="com.xray.tv.input.MainActivity" android:setupActivity="com.xray.tv.input.MainActivity" /> 複製代碼
在xml/richtvinputservice中配置了兩個activty,這個是提供LiveTv去打開的,好比第一次啓動這個源時,須要啓動到setupActivity所指定的activity,設置時須要啓動到
settingsActivity配置的activity. 更多的細節請查看官方的文檔開發 TV 輸入服務.這個不是本文的重點。
LiveTv實際上是個系統應用,通常由設備廠商提供。在aosp中有一個參考的應用LiveTv.
LiveTv中的代碼主要邏輯就是讀取Tv Provider中內容,經過TvManager和TvInput進行交互。具體文檔請看TvInput框架
經過上面的介紹,大體瞭解了TIF的使用步驟,那它是怎麼工做的,原理是怎麼樣的?咱們在LiveTv和TvInput中用到的一個很重要的類就是TvInputManager.首先看看它的實現
TvInputManager的獲取
TvInputManager tvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
複製代碼
TvInputManager只是咱們當前進程的代理,它的真正實現實際上是一個系統的Service,因此咱們能夠知道這個service其實在system_server進程中,在類TvInputManagerService中實現。因爲這個地方是跨進程通訊,其實它使用的是aidl的方式,因此咱們能夠找到TvInputManager在aidl中定義的接口
# frameworks/base/media/java/android/media/tv/ITvInputManager.aidl
interface ITvInputManager {
List<TvInputInfo> getTvInputList(int userId);
TvInputInfo getTvInputInfo(in String inputId, int userId);
void updateTvInputInfo(in TvInputInfo inputInfo, int userId);
int getTvInputState(in String inputId, int userId);
//省略若干...
...
}
複製代碼
它的實現是在TvInputManagerService的內部類BinderService中。
# frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java
private final class BinderService extends ITvInputManager.Stub {
@Override
public List<TvInputInfo> getTvInputList(int userId) {
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Binder.getCallingUid(), userId, "getTvInputList");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
UserState userState = getOrCreateUserStateLocked(resolvedUserId);
List<TvInputInfo> inputList = new ArrayList<>();
for (TvInputState state : userState.inputMap.values()) {
inputList.add(state.info);
}
return inputList;
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public TvInputInfo getTvInputInfo(String inputId, int userId) {
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Binder.getCallingUid(), userId, "getTvInputInfo");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
UserState userState = getOrCreateUserStateLocked(resolvedUserId);
TvInputState state = userState.inputMap.get(inputId);
return state == null ? null : state.info;
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
...
}
複製代碼
那麼BinderService實例化在什麼地方呢?這就涉及到TvInputManagerService這個系統service的啓動。
TvInputManagerService是在SystemServer中啓動的,具體在SystemServer類的startOtherServices方法中
# frameworks/base/services/java/com/android/server/SystemServer.java
/**
* Starts a miscellaneous grab bag of stuff that has yet to be refactored and organized.
*/
private void startOtherServices() {
...
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_LIVE_TV)
|| mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
traceBeginAndSlog("StartTvInputManager");
mSystemServiceManager.startService(TvInputManagerService.class);
traceEnd();
}
...
}
複製代碼
# frameworks/base/services/core/java/com/android/server/SystemServiceManager.java
public <T extends SystemService> T startService(Class<T> serviceClass) {
try {
final String name = serviceClass.getName();
Slog.i(TAG, "Starting " + name);
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartService " + name);
// Create the service.
if (!SystemService.class.isAssignableFrom(serviceClass)) {
throw new RuntimeException("Failed to create " + name
+ ": service must extend " + SystemService.class.getName());
}
final T service;
try {
Constructor<T> constructor = serviceClass.getConstructor(Context.class);
service = constructor.newInstance(mContext);
} catch (InstantiationException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service could not be instantiated", ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service must have a public constructor with a Context argument", ex);
} catch (NoSuchMethodException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service must have a public constructor with a Context argument", ex);
} catch (InvocationTargetException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service constructor threw an exception", ex);
}
startService(service);
return service;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
}
複製代碼
調用到SystemServiceManager的startService方法
# frameworks/base/services/core/java/com/android/server/SystemServiceManager.java
public void startService(@NonNull final SystemService service) {
// Register it.
mServices.add(service);
// Start it.
long time = SystemClock.elapsedRealtime();
try {
//這裏調用了TvInputManagerService的onStart()
service.onStart();
} catch (RuntimeException ex) {
throw new RuntimeException("Failed to start service " + service.getClass().getName()
+ ": onStart threw an exception", ex);
}
warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStart");
}
複製代碼
以後調用到TvInputManagerService的onStart()方法
# frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java
@Override
public void onStart() {
publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
}
複製代碼
看見了沒,BinderService實例化了。這裏是咱們TvInputManager的真正實現。
在上面的章節,咱們建立TvInput應用的時候建立了一個TvInputService,還建立了一個TvInputService.Session, 而且實現了Session裏的方法,好比提供播放器等等(播放器是TvInput提供的,可是展現的頁面在LiveTv中)。那麼TIF是怎麼和TvInputService取得交流的?,如今咱們研究一下TvManagerService的實現。
爲何要監測安裝包的狀態,由於TvInput常常是以第三方應用的方式實現的,當TvInput應用安裝時,TvInputManagerService會檢測安裝包中是否包含TvInputService。
# frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java
private void registerBroadcastReceivers() {
PackageMonitor monitor = new PackageMonitor() {
private void buildTvInputList(String[] packages) {
synchronized (mLock) {
if (mCurrentUserId == getChangingUserId()) {
buildTvInputListLocked(mCurrentUserId, packages);
buildTvContentRatingSystemListLocked(mCurrentUserId);
}
}
}
@Override
public void onPackageUpdateFinished(String packageName, int uid) {
if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
// This callback is invoked when the TV input is reinstalled.
// In this case, isReplacing() always returns true.
buildTvInputList(new String[] { packageName });
}
...
}
複製代碼
當有安裝包安裝時,監測其中是否有TvInputService,而且權限符合則綁定這個Service.
# frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java
private void buildTvInputListLocked(int userId, String[] updatedPackages) {
UserState userState = getOrCreateUserStateLocked(userId);
userState.packageSet.clear();
if (DEBUG) Slog.d(TAG, "buildTvInputList");
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> services = pm.queryIntentServicesAsUser(
new Intent(TvInputService.SERVICE_INTERFACE),
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
userId);
List<TvInputInfo> inputList = new ArrayList<>();
for (ResolveInfo ri : services) {
ServiceInfo si = ri.serviceInfo;
//檢測是否有android.permission.BIND_TV_INPUT這個權限
if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
+ android.Manifest.permission.BIND_TV_INPUT);
continue;
}
ComponentName component = new ComponentName(si.packageName, si.name);
if (hasHardwarePermission(pm, component)) {
ServiceState serviceState = userState.serviceStateMap.get(component);
if (serviceState == null) {
// New hardware input found. Create a new ServiceState and connect to the
// service to populate the hardware list.
serviceState = new ServiceState(component, userId);
userState.serviceStateMap.put(component, serviceState);
updateServiceConnectionLocked(component, userId);
} else {
inputList.addAll(serviceState.hardwareInputMap.values());
}
} else {
try {
TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
inputList.add(info);
} catch (Exception e) {
Slog.e(TAG, "failed to load TV input " + si.name, e);
continue;
}
}
userState.packageSet.add(si.packageName);
}
Map<String, TvInputState> inputMap = new HashMap<>();
for (TvInputInfo info : inputList) {
if (DEBUG) {
Slog.d(TAG, "add " + info.getId());
}
TvInputState inputState = userState.inputMap.get(info.getId());
if (inputState == null) {
inputState = new TvInputState();
}
inputState.info = info;
inputMap.put(info.getId(), inputState);
}
for (String inputId : inputMap.keySet()) {
if (!userState.inputMap.containsKey(inputId)) {
notifyInputAddedLocked(userState, inputId);
} else if (updatedPackages != null) {
// Notify the package updates
ComponentName component = inputMap.get(inputId).info.getComponent();
for (String updatedPackage : updatedPackages) {
if (component.getPackageName().equals(updatedPackage)) {
//綁定TvInputService
updateServiceConnectionLocked(component, userId);
notifyInputUpdatedLocked(userState, inputId);
break;
}
}
}
}
...
}
複製代碼
綁定第三方自定義的TvInputService
# frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java
private void updateServiceConnectionLocked(ComponentName component, int userId) {
UserState userState = getOrCreateUserStateLocked(userId);
ServiceState serviceState = userState.serviceStateMap.get(component);
if (serviceState == null) {
return;
}
if (serviceState.reconnecting) {
if (!serviceState.sessionTokens.isEmpty()) {
// wait until all the sessions are removed.
return;
}
serviceState.reconnecting = false;
}
boolean shouldBind;
if (userId == mCurrentUserId) {
shouldBind = !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
} else {
// For a non-current user,
// if sessionTokens is not empty, it contains recording sessions only
// because other sessions must have been removed while switching user
// and non-recording sessions are not created by createSession().
shouldBind = !serviceState.sessionTokens.isEmpty();
}
if (serviceState.service == null && shouldBind) {
// This means that the service is not yet connected but its state indicates that we
// have pending requests. Then, connect the service.
if (serviceState.bound) {
// We have already bound to the service so we don't try to bind again until after we
// unbind later on.
return;
}
if (DEBUG) {
Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
}
//bind 第三方應用自定義的TvInputService
Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
serviceState.bound = mContext.bindServiceAsUser(
i, serviceState.connection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
new UserHandle(userId));
} else if (serviceState.service != null && !shouldBind) {
// This means that the service is already connected but its state indicates that we have
// nothing to do with it. Then, disconnect the service.
if (DEBUG) {
Slog.d(TAG, "unbindService(service=" + component + ")");
}
mContext.unbindService(serviceState.connection);
userState.serviceStateMap.remove(component);
}
}
複製代碼
到這裏TvInputManagerService就綁定了第三方應用中自定義的TvInputService
TvInputService如今綁定了,那麼TvInputMangerService和TvInputService交互的邏輯就到了ServiceConnection中,它的實如今InputServiceConnection中,如今就看看InputServiceConnection裏的邏輯。
在onServiceConnected成功後,就能夠拿到從TvInputService中獲取的Binder對象.
@Override public void onServiceConnected(ComponentName component, IBinder service) { if (DEBUG) { Slog.d(TAG, "onServiceConnected(component=" + component + ")"); } synchronized (mLock) { UserState userState = mUserStates.get(mUserId); if (userState == null) { // The user was removed while connecting. mContext.unbindService(this); return; } ServiceState serviceState = userState.serviceStateMap.get(mComponent); serviceState.service = ITvInputService.Stub.asInterface(service); // Register a callback, if we need to. if (serviceState.isHardware && serviceState.callback == null) { serviceState.callback = new ServiceCallback(mComponent, mUserId); try { serviceState.service.registerCallback(serviceState.callback); } catch (RemoteException e) { Slog.e(TAG, "error in registerCallback", e); } } List<IBinder> tokensToBeRemoved = new ArrayList<>(); // And create sessions, if any. for (IBinder sessionToken : serviceState.sessionTokens) { if (!createSessionInternalLocked(serviceState.service, sessionToken, mUserId)) { tokensToBeRemoved.add(sessionToken); } } for (IBinder sessionToken : tokensToBeRemoved) { removeSessionStateLocked(sessionToken, mUserId); } for (TvInputState inputState : userState.inputMap.values()) { if (inputState.info.getComponent().equals(component) && inputState.state != INPUT_STATE_CONNECTED) { notifyInputStateChangedLocked(userState, inputState.info.getId(), inputState.state, null); } } if (serviceState.isHardware) { serviceState.hardwareInputMap.clear(); for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) { try { serviceState.service.notifyHardwareAdded(hardware); } catch (RemoteException e) { Slog.e(TAG, "error in notifyHardwareAdded", e); } } for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) { try { serviceState.service.notifyHdmiDeviceAdded(device); } catch (RemoteException e) { Slog.e(TAG, "error in notifyHdmiDeviceAdded", e); } } } } } 複製代碼
和第三方的TvInputService聯通以後,就須要進行交互了,它們之間交互須要建立一個Session,也就是TvInputService.Session,這個Session中的交互是經過ITvInputSessionCallback來實現的,如今看一下ITvInputSessionCallback.aidl這個文件
oneway interface ITvInputSessionCallback { void onSessionCreated(ITvInputSession session, in IBinder hardwareSessionToken); void onSessionEvent(in String name, in Bundle args); void onChannelRetuned(in Uri channelUri); void onTracksChanged(in List<TvTrackInfo> tracks); void onTrackSelected(int type, in String trackId); void onVideoAvailable(); void onVideoUnavailable(int reason); void onContentAllowed(); void onContentBlocked(in String rating); void onLayoutSurface(int left, int top, int right, int bottom); void onTimeShiftStatusChanged(int status); void onTimeShiftStartPositionChanged(long timeMs); void onTimeShiftCurrentPositionChanged(long timeMs); // For the recording session void onTuned(in Uri channelUri); void onRecordingStopped(in Uri recordedProgramUri); void onError(int error); } 複製代碼
這就是咱們在自定義第三方TvInputService時,根據咱們的需求,須要實現的方法。
講到這裏TvInputManager和第三方TvInputService的交互就完成了.
LiveTv和TvInput之間交互還有一種方式就是TvProvider, TvInput應用會將本身的頻道和節目數據寫入TvProvider對應的數據庫中,數據庫的地址在
/data/data/com.android.providers.tv/databases/tv.db
複製代碼
這樣LiveTv就能夠讀取TvProvider中的數據了。固然這裏的數據除了LiveTv和當前的TvInput應用,其餘應用是沒有權限讀取這裏的數據的。
TIF是Android 電視特有的一個部分,可是他和Android Framework的其餘模塊邏輯是相似的,好比AMS、WMS、PMS. 在閱讀源碼的過程當中,咱們能夠了解它爲何這麼設計,好比TIF它自己就是爲了第三方的TvInput能有一套統一的標準Api而設計的。還有TvInputService這塊的設計能夠給咱們在跨進程通訊設計Api時帶來參考。