Android TV输入框架(TIF)原理介绍

前言

Android TV 输入框架 (TIF) 简化了向 Android TV 传送直播内容的过程。Android TIF 为制造商提供了一个标准 API,供他们建立可以控制 Android TV 的输入模块,并让他们能够经过 TV Input 发布的元数据来搜索和推荐直播电视内容。java

概念解释

什么是LiveTv

LiveTv是Android TV系统中的一个TV应用,它是一个系统应用,Aosp中提供了一个参考的LiveTv,那么什么是LiveTv,它和普通的TV应用有什么区别呢?简单一句就是:它向用户展现直播电视内容。可是LiveTv这个应用自己不提供这些直播的数据,它主要的功能是作展现。那么这些内容哪里来呢,答案是Tv Input.android

什么是TvInput

TvInput就是咱们上面LiveTv直播内容的数据来源,这些来源既能够是硬件来源(例如 HDMI 端口和内置调谐器)中的直播视频内容,也能够是软件来源(例如经过互联网在线播放的内容)中的直播视频内容,软件来源通常是个独立的apk,咱们把它叫Input应用。有了这些输入来源,咱们就能够在LiveTv中设置这些来源,而后将它们输入的内容展现到LiveTv中。git

什么是Tv Input Framework(TIF)

有了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应用

前面 的概念解释中已经说过,TvInput应用是直播内容的输入来源,那该怎么建立这个应用呢?其实主要就是建立一个咱们自定义的Service,而这个Service要继承系统的TvInputService,固然为了简化这个过程咱们可使用android官方提供的TIF 随播内容库:session

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交互,最后在清单文件中配置以下:app

<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个地方的改动:框架

  1. 添加权限:ide

    android:permission="android.permission.BIND_TV_INPUT"
    复制代码
  2. 添加过滤器action

    <intent-filter>
                    <action android:name="android.media.tv.TvInputService" />
                </intent-filter>
    复制代码
  3. 添加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

LiveTv实际上是个系统应用,通常由设备厂商提供。在aosp中有一个参考的应用LiveTv.

LiveTv中的代码主要逻辑就是读取Tv Provider中内容,经过TvManager和TvInput进行交互。具体文档请看TvInput框架

TIF工做流程介绍

经过上面的介绍,大体了解了TIF的使用步骤,那它是怎么工做的,原理是怎么样的?咱们在LiveTv和TvInput中用到的一个很重要的类就是TvInputManager.首先看看它的实现

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 启动

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();
            }
	...

 }
复制代码

经过反射实例化TvInputManagerService

# 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的真正实现。

绑定TvInputService

在上面的章节,咱们建立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

InputServiceConnection

TvInputService如今绑定了,那么TvInputMangerService和TvInputService交互的逻辑就到了ServiceConnection中,它的实如今InputServiceConnection中,如今就看看InputServiceConnection里的逻辑。

onServiceConnected

在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的交互就完成了.

TvProvider

LiveTv和TvInput之间交互还有一种方式就是TvProvider, TvInput应用会将本身的频道和节目数据写入TvProvider对应的数据库中,数据库的地址在

/data/data/com.android.providers.tv/databases/tv.db
复制代码

这样LiveTv就能够读取TvProvider中的数据了。固然这里的数据除了LiveTv和当前的TvInput应用,其余应用是没有权限读取这里的数据的。

参考Demo

总结

TIF是Android 电视特有的一个部分,可是他和Android Framework的其余模块逻辑是相似的,好比AMS、WMS、PMS. 在阅读源码的过程当中,咱们能够了解它为何这么设计,好比TIF它自己就是为了第三方的TvInput能有一套统一的标准Api而设计的。还有TvInputService这块的设计能够给咱们在跨进程通讯设计Api时带来参考。

相关文章
相关标签/搜索