Android TV Input Framework(TIF)--2 構建TV input list

分類:編程 時間:2016-11-08
[摘要:TvInputManagerService治理著體系的種種輸進,TV Input首要分為三品種型: hardware input:首要包括TV內建的種種輸進端心,比方tuner、component, composite, hdmi。 非ha]

TvInputManagerService管理著系統的各種輸入,TV Input主要分為三種類型:

  • hardware input:主要包含TV內建的各種輸入端口,比如tuner、component, composite, hdmi。

  • 非hardware input: 視頻點播等非內建的硬件端口屬於這種類型。

  • HDMI logic input:帶有HDMI CEC的設備屬於這種類型。


TvInputManagerService由systemServer創建,我們先看看它的構造方法

  public TvInputManagerService(Context context) {
        super(context);
        ...
        mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());

        synchronized (mLock) {
            mUserStates.put(mCurrentUserId, new UserState(mContext, mCurrentUserId));
        }
    }

在構造方法中會創建一下TvInputHardwareManager實例,並傳入一個HardwareListener實例給TvInputHardwareManager。TvInputHardwareManager通過TvInputHal來獲取TV硬件輸入的各種狀態,並通過HardwareListener通知TvInputManagerService。

class TvInputHardwareManager implements TvInputHal.Callback

public interface Callback {
        public void onDeviceAvailable(
                TvInputHardwareInfo info, TvStreamConfig[] configs);
        public void onDeviceUnavailable(int deviceId);
        public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs);
        public void onFirstFrameCaptured(int deviceId, int streamId);
    }

 public TvInputHardwareManager(Context context, Listener listener) {
        mContext = context;
        mListener = listener;
        ...
        mHal.init();
    }

TvInputHardwareManager實現了TvInputHal.Callback接口,在構造方法中調用mHal.init()對TvInputHal進行初始化,在TvInputHal初始化過程中,所有TV內建的Input都會通過onDeviceAvailable通知給TvInputHardWareManager,我們看一下onDeviceAvailable的實現:

<code class="hljs r has-numbering">  public void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs) {
        synchronized (mLock) {
            <span class="hljs-keyword">...</span>
            buildHardwareListLocked();
            mHandler.obtainmessage(
                    ListenerHandler.HARDWARE_DEVICE_ADDED, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, info).sendToTarget();
            <span class="hljs-keyword">...</span>
            }
        }
    }</code>


onDeviceAvailable會調用buildHardwareListLocked把Tv Input的信息放入一個鏈表,TV Input的信息用TvInputHardwareInfo類表示,

<code class="hljs cs has-numbering"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">buildHardwareListLocked</span>() {
        mHardwareList.clear();
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < mConnections.size(); ++i) {
            mHardwareList.add(mConnections.valueAt(i).getHardwareInfoLocked());
        }
    }</code>

然後通過mHandler.obtainMessage(ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget()發送到handler線程處理,

<code class="hljs r has-numbering"> public final void handleMessage(Message msg) {
            <span class="hljs-keyword">switch</span> (msg.what) {
                <span class="hljs-keyword">...</span>
                case HARDWARE_DEVICE_ADDED: {
                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
                    mListener.onHardwareDeviceAdded(info);
                    <span class="hljs-keyword">break</span>;
                }
                <span class="hljs-keyword">...</span></code>

還記得TvInputManagerService在創建TvInputHardwareManager的時候傳入的HardwareListener嗎?它就是mListener, HardwareListener的onHardwareDeviceAdded會被調用,

<code class="hljs Java has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onHardwareDeviceAdded</span>(TvInputHardwareInfo info) {
            <span class="hljs-keyword">synchronized</span> (mLock) {
                UserState userState = getUserStateLocked(mCurrentUserId);
                <span class="hljs-comment">// Broadcast the event to all hardware inputs.</span>
                <span class="hljs-keyword">for</span> (ServiceState serviceState : userState.serviceStateMap.values()) {
                    <span class="hljs-keyword">if</span> (!serviceState.isHardware || serviceState.service == <span class="hljs-keyword">null</span>) <span class="hljs-keyword">continue</span>;
                    <span class="hljs-keyword">try</span> {
                        serviceState.service.notifyHardwareAdded(info);
                    } <span class="hljs-keyword">catch</span> (RemoteException e) {
                        Slog.e(TAG, <span class="hljs-string">"error in notifyHardwareAdded"</span>, e);
                    }
                }
            }
        }</code>


到目前為止,我們只是對TvInputManagerService的構造方法進行分析,userState.serviceStateMap還是空的,所以這個時候onHardwareDeviceAdded被調用其實什麽事情都沒有做。


我們接著分析TvInputManagerService的初始化過程:

<code class="hljs java has-numbering"> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onBootPhase</span>(<span class="hljs-keyword">int</span> phase) {
        <span class="hljs-keyword">if</span> (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
            registerBroadcastReceivers();
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
            <span class="hljs-keyword">synchronized</span> (mLock) {
                buildTvInputListLocked(mCurrentUserId, <span class="hljs-keyword">null</span>);
                buildTvContentRatingSystemListLocked(mCurrentUserId);
            }
        }
        mTvInputHardwareManager.onBootPhase(phase);
    }</code>


當第三方的app可以啟動的時候,即phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START,調用buildTvInputListLocked開始構建Tv Input List。


<code class="hljs cs has-numbering"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">buildTvInputListLocked</span>(<span class="hljs-keyword">int</span> userId, String[] updatedpackages) {
        UserState userState = getUserStateLocked(userId);
        userState.packageSet.clear();

        <span class="hljs-keyword">if</span> (DEBUG) Slog.d(TAG, <span class="hljs-string">"buildTvInputList"</span>);
        PackageManager pm = mContext.getPackageManager();
        List<ResolveInfo> services = pm.queryIntentServices(
                <span class="hljs-keyword">new</span> Intent(TvInputService.SERVICE_INTERFACE),
                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);                       ---<<span class="hljs-number">1</span>>
        List<TvInputInfo> inputList = <span class="hljs-keyword">new</span> ArrayList<TvInputInfo>();
        <span class="hljs-keyword">for</span> (ResolveInfo ri : services) {
            ServiceInfo si = ri.serviceInfo;
            <span class="hljs-keyword">if</span> (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
                Slog.w(TAG, <span class="hljs-string">"Skipping TV input "</span> + si.name + <span class="hljs-string">": it does not require the permission "</span>
                        + android.Manifest.permission.BIND_TV_INPUT);
                <span class="hljs-keyword">continue</span>;
            }
            ComponentName component = <span class="hljs-keyword">new</span> ComponentName(si.packageName, si.name);
            <span class="hljs-keyword">if</span> (hasHardwarePermission(pm, component)) {
                ServiceState serviceState = userState.serviceStateMap.<span class="hljs-keyword">get</span>(component);
                <span class="hljs-keyword">if</span> (serviceState == <span class="hljs-keyword">null</span>) {
                    <span class="hljs-comment">// We see this hardware TV input service for the first time; we need to</span>
                    <span class="hljs-comment">// prepare the ServiceState object so that we can connect to the service and</span>
                    <span class="hljs-comment">// let it add TvInputInfo objects to mInputList if there's any.</span>
                    serviceState = <span class="hljs-keyword">new</span> ServiceState(component, userId);                     ---<<span class="hljs-number">2</span>>
                    userState.serviceStateMap.put(component, serviceState);
                    updateServiceConnectionLocked(component, userId);    ---<<span class="hljs-number">3</span>>
                } <span class="hljs-keyword">else</span> {
                    inputList.addAll(serviceState.inputList);            ---<<span class="hljs-number">4</span>>
                }
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">try</span> {
                    inputList.add(TvInputInfo.createTvInputInfo(mContext, ri));---<<span class="hljs-number">5</span>>
                } <span class="hljs-keyword">catch</span> (XmlPullParserException | IOException e) {
                    Slog.e(TAG, <span class="hljs-string">"failed to load TV input "</span> + si.name, e);
                    <span class="hljs-keyword">continue</span>;
                }
            }
            userState.packageSet.add(si.packageName);
        }

        Map<String, TvInputState> inputMap = <span class="hljs-keyword">new</span> HashMap<String, TvInputState>();             ---<<span class="hljs-number">6</span>>
        <span class="hljs-keyword">for</span> (TvInputInfo info : inputList) {
            <span class="hljs-keyword">if</span> (DEBUG) {
                Slog.d(TAG, <span class="hljs-string">"add "</span> + info.getId());
            }
            TvInputState state = userState.inputMap.<span class="hljs-keyword">get</span>(info.getId());
            <span class="hljs-keyword">if</span> (state == <span class="hljs-keyword">null</span>) {
                state = <span class="hljs-keyword">new</span> TvInputState();     
            }
            state.info = info;
            inputMap.put(info.getId(), state);       
        }

        <span class="hljs-keyword">for</span> (String inputId : inputMap.keySet()) {
            <span class="hljs-keyword">if</span> (!userState.inputMap.containsKey(inputId)) {              ---<<span class="hljs-number">7</span>>
                notifyInputAddedLocked(userState, inputId);
            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (updatedPackages != <span class="hljs-keyword">null</span>) {
                <span class="hljs-comment">// Notify the package updates</span>
                ComponentName component = inputMap.<span class="hljs-keyword">get</span>(inputId).info.getComponent();
                <span class="hljs-keyword">for</span> (String updatedPackage : updatedPackages) {
                    <span class="hljs-keyword">if</span> (component.getPackageName().equals(updatedPackage)) {
                        updateServiceConnectionLocked(component, userId);
                        notifyInputUpdatedLocked(userState, inputId);
                        <span class="hljs-keyword">break</span>;
                    }
                }
            }
        }

        <span class="hljs-keyword">for</span> (String inputId : userState.inputMap.keySet()) {
            <span class="hljs-keyword">if</span> (!inputMap.containsKey(inputId)) {    ---<<span class="hljs-number">8</span>>
                TvInputInfo info = userState.inputMap.<span class="hljs-keyword">get</span>(inputId).info;
                ServiceState serviceState = userState.serviceStateMap.<span class="hljs-keyword">get</span>(info.getComponent());
                <span class="hljs-keyword">if</span> (serviceState != <span class="hljs-keyword">null</span>) {
                    abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
                }
                notifyInputRemovedLocked(userState, inputId);
            }
        }

        userState.inputMap.clear();
        userState.inputMap = inputMap;               ---<<span class="hljs-number">9</span>>
    }</code>


這個方法比較長,關鍵行標上了序號,下面逐一解釋:

1、查找所有的package中的service,如果package的service在AndroidManifest.xml中聲明了如下屬性:

<code class="hljs avrasm has-numbering"><span class="hljs-label">android:</span>permission=<span class="hljs-string">"android.permission.BIND_TV_INPUT"</span></code>
那麽就認為這個service代表一種Tv Input,這些service都繼承自TvInputService。對於TV內建的輸入端口,還要在package的AndroidManifest.xml聲明

<code class="hljs xml has-numbering"><span class="hljs-tag"><<span class="hljs-title">uses-permission</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">"android.permission.TV_INPUT_HARDWARE"</span> /></span></code>
2、對於內建的硬件輸入端口,TvInputManangerService會創建對應的ServiceState實例,並跟對應的Service建立連接,

<code class="hljs cs has-numbering">  <span class="hljs-keyword">private</span> <span class="hljs-title">ServiceState</span>(ComponentName component, <span class="hljs-keyword">int</span> userId) {
            <span class="hljs-keyword">this</span>.component = component;
            <span class="hljs-keyword">this</span>.connection = <span class="hljs-keyword">new</span> InputServiceConnection(component, userId);
            <span class="hljs-keyword">this</span>.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
        }</code>
註意this.connection = new InputServiceConnection(component, userId),稍後會用到。

<code class="hljs r has-numbering"> private void updateServiceConnectionLocked(ComponentName component, int userId) {
     <span class="hljs-keyword">...</span>
            Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
            serviceState.bound = mContext.bindServiceAsUser(
                    i, serviceState.connection, Context.BIND_AUTO_CREATE, new UserHandle(userId));
       <span class="hljs-keyword">...</span>
    }</code>
3.在updateServiceConnectionLocked中通過Intent跟service建立連接,這個時候inputList是空的,buildTvInputListLocked繼續執行返回。當跟service連接成功以後,serviceState.connection.onServiceConnected會被回調,就是前面說的InputServiceConnection.onServiceConnected。

<code class="hljs r has-numbering">public void onServiceConnected(ComponentName component, IBinder service) {
          <span class="hljs-keyword">...</span>
                // Register a callback, <span class="hljs-keyword">if</span> we need to.
                <span class="hljs-keyword">if</span> (serviceState.isHardware && serviceState.callback == null) {
                    serviceState.callback = new ServiceCallback(mComponent, mUserId);
                    <span class="hljs-keyword">try</span> {
                        serviceState.service.registerCallback(serviceState.callback);
                    } catch (RemoteException e) {
                        Slog.e(TAG, <span class="hljs-string">"error in registerCallback"</span>, e);
                    }
                }
               <span class="hljs-keyword">...</span>
                <span class="hljs-keyword">if</span> (serviceState.isHardware) {
                    List<TvInputHardwareInfo> hardwareInfoList =
                            mTvInputHardwareManager.getHardwareList();
                    <span class="hljs-keyword">for</span> (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
                        <span class="hljs-keyword">try</span> {
                            serviceState.service.notifyHardwareAdded(hardwareInfo);
                        } catch (RemoteException e) {
                            Slog.e(TAG, <span class="hljs-string">"error in notifyHardwareAdded"</span>, e);
                        }
                    }

                    List<HdmiDeviceInfo> deviceInfoList =
                            mTvInputHardwareManager.getHdmiDeviceList();
                    <span class="hljs-keyword">for</span> (HdmiDeviceInfo deviceInfo : deviceInfoList) {
                        <span class="hljs-keyword">try</span> {
                            serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
                        } catch (RemoteException e) {
                            Slog.e(TAG, <span class="hljs-string">"error in notifyHdmiDeviceAdded"</span>, e);
                        }
                    }
                }
            }
        }</code>

接著會向Service註冊callback,後面會用到。然後通過mTvInputHardwareManager.getHardwareList獲取之前初始化的時候保存的Hardware list,調用service的notifyHardwareAdded方法,我們看一下TvInputService的notifyHardwareAdded實現:

<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">notifyHardwareAdded</span>(TvInputHardwareInfo hardwareInfo) {
                mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HARDWARE_TV_INPUT,
                        hardwareInfo).sendToTarget();
            }</code>
通知Handler線程DO_ADD_HARDWARE_TV_INPUT,

<code class="hljs r has-numbering">public final void handleMessage(Message msg) {
            <span class="hljs-keyword">switch</span> (msg.what) {
            <span class="hljs-keyword">...</span>
            case DO_ADD_HARDWARE_TV_INPUT: {
                    TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
                    TvInputInfo inputInfo = onHardwareAdded(hardwareInfo);
                    <span class="hljs-keyword">if</span> (inputInfo != null) {
                        broadcastAddHardwareTvInput(hardwareInfo.getDeviceId(), inputInfo);
                    }
                    <span class="hljs-keyword">return</span>;
                }
                <span class="hljs-keyword">...</span></code>
然後service的onHardwareAdded方法被調用,並返回TvInputInfo,這代表一個TV Input,然後通過broadcastAddHardwareTvInput通知TvInputManagerService,

<code class="hljs cs has-numbering"> <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">broadcastAddHardwareTvInput</span>(<span class="hljs-keyword">int</span> deviceId, TvInputInfo inputInfo) {
            <span class="hljs-keyword">int</span> n = mCallbacks.beginBroadcast();
            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < n; ++i) {
                <span class="hljs-keyword">try</span> {
                    mCallbacks.getBroadcastItem(i).addHardwareTvInput(deviceId, inputInfo);
                } <span class="hljs-keyword">catch</span> (RemoteException e) {
                    Log.e(TAG, <span class="hljs-string">"Error while broadcasting."</span>, e);
                }
            }
            mCallbacks.finishBroadcast();
        }</code>

我們在跟service建立連接以後,註冊了callback,callback的addHardwareTvInput被調用,我們看一下TvInputManagerService中addHardwareTvInput的實現,


<code class="hljs java has-numbering"> <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addTvInputLocked</span>(TvInputInfo inputInfo) {
            ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
            serviceState.inputList.add(inputInfo);
            buildTvInputListLocked(mUserId, <span class="hljs-keyword">null</span>);
        }

        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addHardwareTvInput</span>(<span class="hljs-keyword">int</span> deviceId, TvInputInfo inputInfo) {
            ensureHardwarePermission();
            ensureValidInput(inputInfo);
            <span class="hljs-keyword">synchronized</span> (mLock) {
                mTvInputHardwareManager.addHardwareTvInput(deviceId, inputInfo);
                addTvInputLocked(inputInfo);
            }
        }</code>
addHardwareTvInput會調用addTvInputLocked,addTvInputLocked把TvInputInfo存入serviceState.inputList,接著調用buildTvInputListLocked重新構建Tv input list。


4.再次調用buildTvInputListLocked的時候,serviceState不為null,把serviceState.inputList放入到inputList。
5. 如果不是TV內建的硬件Input,直接創建TvInputInfo並放入InputList。
6. 每一個Tv Input都對應一個TvInputState,通過inputId在inputMap中索引。
7. 如果新構建的inputMap中的inputId在userState.inputMap中沒有,表明這個TV input是新增加的,如果TV app有註冊callback,那麽TV app的onInputAdded會被調用。
8. 如果userState.inputMap中的inputId在新構建的inputMap中沒有,表明這個TV Input被移除,如果TV app有註冊callback,那麽TV app的onInputRemoved會被調用。
9. userState.inputMap被新構建的inputMap替換,至此TV input list構建完成。





































Tags: Android interface hardware public super

文章來源:


ads
ads

相關文章
ads

相關文章

ad