所有内容都是自己的分析,现在是简单罗列代码位置及整体结构,细节的东西会慢慢充实,欢迎讨论纠正,我会及时更改。
一、简单背景
简单背景:随着无线互联的深入,不管是蓝牙、WIFI或者各种基于此的规范不管是UPNP还是DLNA都随着用户的需求得到了很大的发展,google 自从android 4.0引入wifi direct后,又在11月份公布的android 4.2中引入了Miracast无线显示共享,其协议在此可以下载。具体的协议部分内容比较多,本人由于水平有限,就不在这里罗列协议的内容了,只附上一份架构图供大家对其有个大致的印象。
英文缩写对应如下:
HIDC: Human Interface Device Class
UIBC: User Input Back Channel
PES: Packetized Elementary Stream
HDCP: High-bandwidth Digital Content Protection
MPEG2-TS: Moving Picture Experts Group 2 Transport Stream
RTSP: Real-Time Streaming Protocol
RTP: Real-time Transport Protocol
Wi-Fi P2P: Wi-Fi Direct
TDLS: Tunneled Direct Link Setup
二、应用层简介
好了,接下来首先来看一看android 4.2 提供了哪些与其相关的应用:
首先,需要注意的自然是API文档中公布的 http://developer.android.com/about/versions/android-4.2.html#SecondaryDisplays
Presentation应用,在源码中路径为:development/samples/ApiDemos/src/com/example/android/apis/app/下面的两个文件
PresentationActivity.java
以及 PresentationWithMediaRouterActivity.java 。
这两个应用所使用的Presentation基类在frameworks/base/core/java/android/app/Presentation.java,可以看到其继承了dialog类,并复用了如show()以及cancel()函数。
由于官方文档已经有了关于Presentation以及MediaRouter的简要介绍,这里先不再结合framework层详细介绍,以后有机会一并再结合源码分析一下。
简单来说,Display Manager 可以列举出可以直连显示的多个设备,MediaRouter提供了快速获得系统中用于演示(presentations)默认显示设备的方法。可以利用
frameworks/base/media/java/android/media/MediaRouter.java下的getSelectedRoute(int type){ }函数来获得当前所选择type类型的Router信息。对于PresentationWithMediaRouterActivity应用而言,
- MediaRouter mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
- MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO);
- Display presentationDisplay = route != null ? route.getPresentationDisplay() : null;
接下来,只要将该Display对象作为自己重构的演示(Presentation)类构造函数参数传入,这样自己重构的演示就会出现在第二个显示设备上。
- mPresentation = new DemoPresentation(this, presentationDisplay);
- ...
- try {
- mPresentation.show();
- } catch (WindowManager.InvalidDisplayException ex) {
- Log.w(TAG, "Couldn't show presentation! Display was removed in "
- + "the meantime.", ex);
- mPresentation = null;
- }
- }
- ...
- private final static class DemoPresentation extends Presentation {
- ...
- public DemoPresentation(Context context, Display display) {
- super(context, display);
- }
- ...
- }
为了进一步优化附加显示设备自定义演示UI的显示效果,你可以在<style>属性中指定相关应用主题为android:presentationTheme。
为了在运行时检测外设显示设备的连接状态,你需要在自己的实现类中创建一个 MediaRouter.SimpleCallback的一个实例,该实例中需要自己实现onRoutePresentationDisplayChanged() 等回调函数。当一个新的演示显示设备连接时,系统就会回调该函数,进一步其就会调用上面提到的MediaRouter.getSelectedRoute()函数。
- private final MediaRouter.SimpleCallback mMediaRouterCallback =
- new MediaRouter.SimpleCallback() {
- public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
- updatePresentation();
- }
- public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
- updatePresentation();
- }
- public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) {
- updatePresentation();
- }
- };
当然,使用者需要使用MediaRouter.addCallback()函数完成注册,如同在PresentationWithMediaRouterActivity应用中,
- mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_LIVE_VIDEO, mMediaRouterCallback);
这里可以简单看看调用流程,首先可以看到onRoutePresentationDisplayChanged()回调函数在MediaRouter.java会先触发dispatchRoutePresentationDisplayChanged()函数,
frameworks/base/media/java/android/media/MediaRouter.java
- static void dispatchRoutePresentationDisplayChanged(RouteInfo info) {
- for (CallbackInfo cbi : sStatic.mCallbacks) {
- if ((cbi.type & info.mSupportedTypes) != 0) {
- cbi.cb.onRoutePresentationDisplayChanged(cbi.router, info);
- }
- }
- }
进一步可以看到该分发函数被用于更新演示设备的状态,并将其提供给frameworks/base/core/java/android/app/Presentation.java中注册的监听函数,
frameworks/base/media/java/android/media/MediaRouter.java
- private void updatePresentationDisplays(int changedDisplayId) {
- final Display[] displays = getAllPresentationDisplays();
- final int count = mRoutes.size();
- for (int i = 0; i < count; i++) {
- final RouteInfo info = mRoutes.get(i);
- Display display = choosePresentationDisplayForRoute(info, displays); //根据displays的地址信息从所有显示类型为Presentation displays的设备中选择对应的显示设备
- if (display != info.mPresentationDisplay
- || (display != null && display.getDisplayId() == changedDisplayId)) {
- info.mPresentationDisplay = display;
- dispatchRoutePresentationDisplayChanged(info);
- }
- }
- }
- @Override
- public void onDisplayAdded(int displayId) {
- updatePresentationDisplays(displayId);
- }
- @Override
- public void onDisplayChanged(int displayId) {
- updatePresentationDisplays(displayId);
- }
- @Override
- public void onDisplayRemoved(int displayId) {
- updatePresentationDisplays(displayId);
- }
frameworks/base/core/java/android/app/Presentation.java
- @Override
- protected void onStart() {
- super.onStart();
- mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);// Presentation线程一启动就会注册Display Manager中负责监听演示设备变化的三个监听器
- ...
- }
- private final DisplayListener mDisplayListener = new DisplayListener() {
- @Override
- public void onDisplayAdded(int displayId) {
- }
- @Override
- public void onDisplayRemoved(int displayId) {
- if (displayId == mDisplay.getDisplayId()) {
- handleDisplayRemoved();
- }
- }
- @Override
- public void onDisplayChanged(int displayId) {
- if (displayId == mDisplay.getDisplayId()) {
- handleDisplayChanged();
- }
- }
- };
该注册函数的实现实际是在DisplayManagerGlobal类中,该类主要负责管理显示管理器(Display Manager)与显示管理服务(Display Manager Service)之间的通信。
frameworks/base/core/java/android/hardware/display/DisplayManagerGlobal.java
- public void registerDisplayListener(DisplayListener listener, Handler handler) {
- if (listener == null) {
- throw new IllegalArgumentException("listener must not be null");
- }
- synchronized (mLock) {
- int index = findDisplayListenerLocked(listener);
- if (index < 0) {
- mDisplayListeners.add(new DisplayListenerDelegate(listener, handler)); //给动态数组中增添显示监听处理代理
- registerCallbackIfNeededLocked(); //实际负责注册回调函数的方法
- }
- }
- }
- private void registerCallbackIfNeededLocked() {
- if (mCallback == null) {
- mCallback = new DisplayManagerCallback();
- try {
- mDm.registerCallback(mCallback);
- } catch (RemoteException ex) {
- Log.e(TAG, "Failed to register callback with display manager service.", ex);
- mCallback = null;
- }
- }
- }
可以看到,registerCallbackIfNeededLocked()函数中新建的回调函数实际上是IDisplayManagerCallback的AIDL接口实现,
- private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
- @Override
- public void onDisplayEvent(int displayId, int event) {
- if (DEBUG) {
- Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event);
- }
- handleDisplayEvent(displayId, event);
- }
- }
- package android.hardware.display;
- /** @hide */
- interface IDisplayManagerCallback {
- oneway void onDisplayEvent(int displayId, int event);
- }
之后则是将新建的mCallback作为参数传入IDisplayManager中的registerCallback(in IDisplayManagerCallback callback); 接口函数中。
最后,来看看与IDisplayManager AIDL接口对应的Service实现。
frameworks/base/services/java/com/android/server/display/DisplayManagerService.java
- @Override // Binder call
- public void registerCallback(IDisplayManagerCallback callback) {
- if (callback == null) {
- throw new IllegalArgumentException("listener must not be null");
- }
- synchronized (mSyncRoot) {
- int callingPid = Binder.getCallingPid();
- if (mCallbacks.get(callingPid) != null) {
- throw new SecurityException("The calling process has already "
- + "registered an IDisplayManagerCallback.");
- }
- CallbackRecord record = new CallbackRecord(callingPid, callback);
- try {
- IBinder binder = callback.asBinder();
- binder.linkToDeath(record, 0);
- } catch (RemoteException ex) {
- // give up
- throw new RuntimeException(ex);
- }
- mCallbacks.put(callingPid, record);
- }
- }
可以看到该服务采取同步机制,这是因为display manager可能同时被多个线程访问,这里所有属于display manager的对象都会使用同一把锁。本服务将该锁称为同步root,其有唯一的类型DisplayManagerService.SyncRoot,所有需要该锁的方法都会以“Locked"作为后缀。另外,CallbackRecord函数会绑定IDisplayManagerCallback接口。并且通过函数notifyDisplayEventAsync( )向回调函数提供显示事件通知,事件类型分为三类EVENT_DISPLAY_ADDED,EVENT_DISPLAY_CHANGED以及EVENT_DISPLAY_REMOVED等。
函数notifyDisplayEventAsync( )会在DisplayManager处理线程DisplayManagerHandler中,当msg类型为MSG_DELIVER_DISPLAY_EVENT时被函数deliverDisplayEvent( )调用。进一步而言,类型为MSG_DELIVER_DISPLAY_EVENT的消息,是由函数void sendDisplayEventLocked(int displayId, int event)发送的。该函数的使用者addLogicalDisplayLocked()以及 updateLogicalDisplaysLocked( )正好通过sendDisplayEventLocked( )将显示设备ID displayid 与 三种显示事件类型联系在了一起。最后正是函数handleDisplayDeviceAdded( )、handleDisplayDeviceChanged()以及handleDisplayDeviceRemoved()将显示设备的变化状态通过注册在显示管理服务中的监听器DisplayAdapter.Listener以异步方式传递给Display adapter。
- private final class DisplayAdapterListener implements DisplayAdapter.Listener {
- @Override
- public void onDisplayDeviceEvent(DisplayDevice device, int event) {
- switch (event) {
- case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED:
- handleDisplayDeviceAdded(device);
- break;
- case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED:
- handleDisplayDeviceChanged(device);
- break;
- case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED:
- handleDisplayDeviceRemoved(device);
- break;
- }
- }
- ...
- }
这样将服务与显示设备适配器分离的做法有两方面优点,其一方面简洁的封装了两个类的不同职责:显示适配器负责处理各个显示设备而显示管理服务则负责处理全局的状态变化;另一方面,其将会避免在异步搜索显示设备时导致的死锁问题。
接下来,让我们进入正题,来通过WiFi Display Setting应用来大致分析一下Wifidisplay具体的流程,其在源码目录下
packages/apps/Settings/src/com/android/settings/wfd/WifiDisplaySettings.java
关于此应用的细节这里就不再详述,我们首先来看Wifi Display 的设备发现,这里首先需要查看Wifi Display的设备状态,通过调用getFeatureState()可以获得进行该操作的设备是否可以支持Wifi Display,以及该功能是否被用户使能等信息。如果此时的设备状态表示Widfi display功能已经开启,那么就开始进行设备发现
- if (mWifiDisplayStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {
- mDisplayManager.scanWifiDisplays();
- }
在搜寻完设备后,用户可以选择设备进行连接,当然正在进行连接或已经连接配对的设备,再次点击配置后,会弹出对话框供用户选择断开连接。
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
- Preference preference) {
- if (preference instanceof WifiDisplayPreference) {
- WifiDisplayPreference p = (WifiDisplayPreference)preference;
- WifiDisplay display = p.getDisplay();
- if (display.equals(mWifiDisplayStatus.getActiveDisplay())) {
- showDisconnectDialog(display);
- } else {
- mDisplayManager.connectWifiDisplay(display.getDeviceAddress());
- }
- }
- return super.onPreferenceTreeClick(preferenceScreen, preference);
- }
最后,该应用还提供对设备重命名以及剔除Wifi Display 设备连接历史信息的方法。
三、Frameworks层分析
首先,从应用层的设备发现来往下分析,我们容易看到,如同对上面PresentationWithMediaRouterActivity应用的分析,这里采取的也是AIDL进程间通信方式。来看一看接口定义文件,
frameworks/base/core/java/android/hardware/display/IDisplayManager.aidl
- package android.hardware.display;
- import android.hardware.display.IDisplayManagerCallback;
- import android.hardware.display.WifiDisplay;
- import android.hardware.display.WifiDisplayStatus;
- import android.view.DisplayInfo;
- /** @hide */
- interface IDisplayManager {
- DisplayInfo getDisplayInfo(int displayId);
- int[] getDisplayIds();
- void registerCallback(in IDisplayManagerCallback callback);
- // No permissions required.
- void scanWifiDisplays();
- // Requires CONFIGURE_WIFI_DISPLAY permission to connect to an unknown device.
- // No permissions required to connect to a known device.
- void connectWifiDisplay(String address);
- // No permissions required.
- void disconnectWifiDisplay();
- // Requires CONFIGURE_WIFI_DISPLAY permission.
- void renameWifiDisplay(String address, String alias);
- // Requires CONFIGURE_WIFI_DISPLAY permission.
- void forgetWifiDisplay(String address);
- // No permissions required.
- WifiDisplayStatus getWifiDisplayStatus();
- }
可以看到,该接口中定义了DisplayManger所需要交互的全部函数,包括设备发现和设备连接等函数。DisplayManager是根据DisplayManagerGlobal提供的单实例来访问相应的接口函数,并与Display manager service建立起联系。以下是DisplayManagerGlobal提供的获取其单例模式的函数,
frameworks/base/core/java/android/hardware/display/DisplayManagerGlobal.java
- public static DisplayManagerGlobal getInstance() {
- synchronized (DisplayManagerGlobal.class) {
- if (sInstance == null) {
- IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);
- if (b != null) {
- sInstance = new DisplayManagerGlobal(IDisplayManager.Stub.asInterface(b));
- //获取DISPLAY_SERVICE服务代理并用于填充构造函数
- }
- }
- return sInstance;
- }
- }
- private final IDisplayManager mDm;
- // AIDL接口对象
- private DisplayManagerGlobal(IDisplayManager dm)
- {
- mDm = dm;
- }
- public void scanWifiDisplays() {
- try {
- mDm.scanWifiDisplays();
- } catch (RemoteException ex) {
- Log.e(TAG, "Failed to scan for Wifi displays.", ex);
- }
- }
frameworks/base/core/java/android/hardware/display/DisplayManager.java
- public DisplayManager(Context context) {
- mContext = context;
- mGlobal = DisplayManagerGlobal.getInstance();
- }
- public void scanWifiDisplays() {
- mGlobal.scanWifiDisplays();
- }
接下来,再看一看scanWifiDisplays()在Display Manager Service中的实现,也就是AIDL接口的实际实现,
frameworks/base/services/java/com/android/server/display/DisplayManagerService.java
- public final class DisplayManagerService extends IDisplayManager.Stub {
- ...
- @Override // Binder call
- public void scanWifiDisplays() {
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mSyncRoot) {
- if (mWifiDisplayAdapter != null) {
- mWifiDisplayAdapter.requestScanLocked();
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- ...
- }
首先在ServerThread.run中通过addService(Context.DISPLAY_SERVICE,…)来注册该服务
frameworks/base/services/java/com/android/server/SystemServer.java
- display = new DisplayManagerService(context, wmHandler, uiHandler);
- ServiceManager.addService(Context.DISPLAY_SERVICE, display, true);
通过display.systemReady(safeMode,onlyCore)来初始化。
当系统属性persist.debug.wfd.enable为1或config_enableWifiDisplay为1,并且不是内核模式和安全模式才进行初始化。该服务是displays的全局管理者,决定如何根据当前链接的物理显示设备来配置逻辑显示器。当状态发生变化时发送通知给系统和应用程序,等等。包括的适配器有OverlayDisplayAdapter,WifiDisplayAdapter,HeadlessDisplayAdapter,LocalDisplayAdapter。对于WifiDisplayAdapter而言,流程大致如下图所示,
frameworks/base/services/java/com/android/server/display/WifiDisplayAdapter.java
- public void requestScanLocked() {
- if (DEBUG) {
- Slog.d(TAG, "requestScanLocked");
- }
- getHandler().post(new Runnable() {
- @Override
- public void run() {
- if (mDisplayController != null) {
- mDisplayController.requestScan();
- }
- }
- });
- }
可以看到此函数又调用了WifiDisplayController类中的requestScan()方法,值得注意的是WifiDisplayController对象必须在handler线程中实例化,该类负责处理控制在WifiDisplayAdapter与WifiP2pManager之间的各种异步操作。
frameworks/base/services/java/com/android/server/display/WifiDisplayController.java
- public void requestScan() {
- discoverPeers();
- }
- private void discoverPeers() {
- if (!mDiscoverPeersInProgress) {
- mDiscoverPeersInProgress = true;
- mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES; //尝试发现配对设备次数,默认值为10
- handleScanStarted();
- tryDiscoverPeers();
- }
- private void handleScanStarted() {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onScanStarted(); //供WifiDisplayAdapter使用的监听器接口函数
- }
- });
- }
- private void tryDiscoverPeers() {
- mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() { //直接调用
- WifiP2pManager接口
- @Override
- public void onSuccess() {
- ...
- mDiscoverPeersInProgress = false;
- requestPeers(); //获得P2P已经配对的设备在判断是否是Wifidisplay设备,如果是加入WifiP2pDevice动态数组中
- }
- @Override
- public void onFailure(int reason) {
- ...
- if (mDiscoverPeersInProgress)
- {
- if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled)
- {
- mHandler.postDelayed(new Runnable()
- { @Override
- public void run()
- {
- if (mDiscoverPeersInProgress)
- {
- if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled)
- {
- mDiscoverPeersRetriesLeft -= 1;
- ...
- tryDiscoverPeers();
- }
- else {
- handleScanFinished();
- mDiscoverPeersInProgress = false;
- }
- }
- }
- }, DISCOVER_PEERS_RETRY_DELAY_MILLIS); //一次发现不成功后延时定长时间后继续尝试连接,递归函数
- } else {
- handleScanFinished();
- mDiscoverPeersInProgress = false;
- }
- }
- }
- });
- }
可以看到,这里直接调用了void discoverPeers(Channel c, ActionListener listener){}函数,该函数发起WIFI对等点发现,该函数会收到发现成功或失败的监听回调。发现过程会一直保持到连接初始化完成或者一个P2P组建立完成。另外,在WifiDisplayController.java中可以看到还注册了WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION这一intent,以确定当p2p peers更改时(即收到WIFI_P2P_PEERS_CHANGED_ACTION广播后),重新获取Wifi Display配对列表,并结束设备发现任务完成相应工作,该流程由函数requestPeers()完成
- private void requestPeers() {
- mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() {
- @Override
- public void onPeersAvailable(WifiP2pDeviceList peers) {
- if (DEBUG) {
- Slog.d(TAG, "Received list of peers.");
- }
- mAvailableWifiDisplayPeers.clear();
- for (WifiP2pDevice device : peers.getDeviceList()) {
- if (DEBUG) {
- Slog.d(TAG, " " + describeWifiP2pDevice(device));
- }
- if (isWifiDisplay(device)) { //根据设备wfdInfo来判断其是否支持wifi display;并且判断其设备类型是否是主sink设备
- mAvailableWifiDisplayPeers.add(device);
- }
- }
- handleScanFinished(); //结束设备发现,对所有符合要求的wifidisplay设备创建Parcelable对象
- }
- });
- }
类似与handleScanStarted()函数,这里结束设备发现任务并且完成相应处理工作的函数handleScanFinished(),也开启监听线程。这些监听线程将在WifiDisplayAdapter被注册使用,
frameworks/base/services/java/com/android/server/display/WifiDisplayAdapter.java
- private final WifiDisplayController.Listener mWifiDisplayListener =
- new WifiDisplayController.Listener() {
- @Override
- public void onFeatureStateChanged(int featureState) {
- synchronized (getSyncRoot()) {
- if (mFeatureState != featureState) {
- mFeatureState = featureState;
- scheduleStatusChangedBroadcastLocked();
- }
- }
- }
- @Override
- public void onScanStarted() {
- synchronized (getSyncRoot()) {
- if (mScanState != WifiDisplayStatus.SCAN_STATE_SCANNING) {
- mScanState = WifiDisplayStatus.SCAN_STATE_SCANNING;
- scheduleStatusChangedBroadcastLocked();
- }
- }
- }
- @Override
- public void onScanFinished(WifiDisplay[] availableDisplays) {
- synchronized (getSyncRoot()) {
- availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(
- availableDisplays);
- if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING
- || !Arrays.equals(mAvailableDisplays, availableDisplays)) {
- mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;
- mAvailableDisplays = availableDisplays;
- scheduleStatusChangedBroadcastLocked();
- }
- }
- }
- ...
- };
可以看到,这些监听接口函数在触发时,都会调用同一个函数scheduleStatusChangedBroadcastLocked(),
- private final WifiDisplayHandler mHandler;
- private void scheduleStatusChangedBroadcastLocked() {
- mCurrentStatus = null;
- if (!mPendingStatusChangeBroadcast) {
- mPendingStatusChangeBroadcast = true;
- mHandler.sendEmptyMessage(MSG_SEND_STATUS_CHANGE_BROADCAST);
- }
- }
- private final class WifiDisplayHandler extends Handler {
- public WifiDisplayHandler(Looper looper) {
- super(looper, null, true /*async*/);
- }
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_SEND_STATUS_CHANGE_BROADCAST:
- handleSendStatusChangeBroadcast();
- break;
- ...
- }
- }
- }
- }
函数scheduleStatusChangedBroadcastLocked()会向内类注册的Handler处理函数发送MSG_SEND_STATUS_CHANGE_BROADCAST消息,处理函数接收到该消息后由handleSendStatusChangeBroadcast()向设备上所有注册过ACTION_WIFI_DISPLAY_STATUS_CHANGED这一intent的接受者发送WifiDisplayStatus广播,
- private void handleSendStatusChangeBroadcast() {
- final Intent intent;
- synchronized (getSyncRoot()) {
- if (!mPendingStatusChangeBroadcast) {
- return;
- }
- mPendingStatusChangeBroadcast = false;
- intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,
- getWifiDisplayStatusLocked());
- }
- // Send protected broadcast about wifi display status to registered receivers.
- getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
- }
最后,我们来看看对于Wifi Display 设备发现最后需要注意的一个部分,即在WifidisplayController中调用的WifiP2pManager中的discoverPeers()接口函数,
frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
- public void discoverPeers(Channel c, ActionListener listener) {
- checkChannel(c);
- c.mAsyncChannel.sendMessage(DISCOVER_PEERS, 0, c.putListener(listener));
- }
当用户在搜寻设备时,该函数会向Channel中发送DISCOVER_PEERS信号,并注册监听器监听响应结果。Channel的初始化在WifiDisplayController的构造函数中由函数Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener){}完成,该函数将P2phandler连接到P2p处理函数框架中。当设备进入P2pEnabledState状态中,并且处理函数接受到DISCOVER_PEERS信号后,真正调用WifiNative的接口函数p2pFind(),并且执行wifi_command()函数调用Wifi设备底层的命令,
frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pService.java
- clearSupplicantServiceRequest();
- if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
- replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED);
- sendP2pDiscoveryChangedBroadcast(true);
- } else {
- replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
- WifiP2pManager.ERROR);
- }
如果p2pFind(int timeout)调用doBooleanCommand("P2P_FIND " + timeout)并且执行成功,则向先前连接的P2pHandler处理函数回复DISCOVER_PEERS_SUCCEEDED信号,并且调用监听函数回调接口((ActionListener) listener).onSuccess(),回调WifidisplayController中的discoverPeers()函数做发现设备成功后的获得设备列表工作即执行函数requestPeers()。最后,还会给在boot之前注册的接收者发送WIFI_P2P_DISCOVERY_CHANGED_ACTION广播。
首先,回顾下应用层,当用户在搜寻完设备后,可以选择设备进行连接,当然正在进行连接或已经连接配对的设备,再次点击配置后,会弹出对话框供用户选择断开连接。
packages/apps/Settings/src/com/android/settings/wfd/WifiDisplaySettings.java
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
- Preference preference) {
- if (preference instanceof WifiDisplayPreference) {
- WifiDisplayPreference p = (WifiDisplayPreference)preference;
- WifiDisplay display = p.getDisplay();
- if (display.equals(mWifiDisplayStatus.getActiveDisplay())) {
- showDisconnectDialog(display);
- } else {
- mDisplayManager.connectWifiDisplay(display.getDeviceAddress());
- }
- }
- return super.onPreferenceTreeClick(preferenceScreen, preference);
- }
如同设备发现的调用流程,当用户选择设备进行连接后,程序会调用DisplayManager的connectWifiDisplay()函数接口。该函数会进一步根据DisplayManagerGlobal提供的单实例对象调用AIDL提供的接口函数connectWifiDisplay(),这又是上一回已经提到过的调用模式。其实际的调用实现是Displaymanager service中提供的connectWifiDisplay()函数,
frameworks/base/services/java/com/android/server/display/DisplayManagerService.java
- public void connectWifiDisplay(String address) {
- if (address == null) {
- throw new IllegalArgumentException("address must not be null");
- }
- final boolean trusted = canCallerConfigureWifiDisplay();
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mSyncRoot) {
- if (mWifiDisplayAdapter != null) {
- mWifiDisplayAdapter.requestConnectLocked(address, trusted);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
到此,我们容易发现连接WifiDisplay设备的函数调用流程与发现设备的流程一致,这里将不做多余解释(详见),在此会罗列出之后的基本流程。
frameworks/base/services/java/com/android/server/display/WifiDisplayAdapter.java
- public void requestConnectLocked(final String address, final boolean trusted) {
- if (DEBUG) {
- Slog.d(TAG, "requestConnectLocked: address=" + address + ", trusted=" + trusted);
- }
- if (!trusted) {
- synchronized (getSyncRoot()) {
- if (!isRememberedDisplayLocked(address)) { //如果设备地址不在保存列表中则忽略不做处理
- ...
- return;
- }
- }
- }
- getHandler().post(new Runnable() {
- @Override
- public void run() {
- if (mDisplayController != null) {
- mDisplayController.requestConnect(address);
- }
- }
- });
- }
frameworks/base/services/java/com/android/server/display/WifiDisplayController.java
- public void requestConnect(String address) {
- for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
- if (device.deviceAddress.equals(address)) {
- connect(device);
- }
- }
- }
- private void connect(final WifiP2pDevice device) {
- if (mDesiredDevice != null
- && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) { //如果设备已经正在连接则返回
- if (DEBUG) {
- ...
- }
- return;
- }
- if (mConnectedDevice != null
- && !mConnectedDevice.deviceAddress.equals(device.deviceAddress)
- && mDesiredDevice == null) {//如果设备已经连接则返回
- if (DEBUG) {
- ...
- }
- return;
- }
- mDesiredDevice = device;
- mConnectionRetriesLeft = CONNECT_MAX_RETRIES; //尝试连接最大次数
- updateConnection();
- }
接下来,我们将重点看一看updateConnection()函数,此函数是建立Wifidisplay连接,监听RTSP连接的核心实现函数。
- private void updateConnection() {
- //在尝试连接到新设备时,需要通知系统这里已经与旧的设备断开连接
- if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
- ...
- mRemoteDisplay.dispose(); //释放NativeRemoteDisplay资源停止监听
- mRemoteDisplay = null; //监听返回对象置为空
- mRemoteDisplayInterface = null; //监听端口置为空
- mRemoteDisplayConnected = false; //连接标识为未连接
- mHandler.removeCallbacks(mRtspTimeout);//将挂起的mRtspTimeout线程从消息队列中移除
- setRemoteSubmixOn(false); //关闭远程混音重建模式
- unadvertiseDisplay();
- }
- if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
- ...
- unadvertiseDisplay();
- final WifiP2pDevice oldDevice = mConnectedDevice;
- mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() {
- @Override
- public void onSuccess() {
- ...
- next();
- }
- @Override
- public void onFailure(int reason) {
- ...
- next();
- }
- private void next() {
- if (mConnectedDevice == oldDevice) { //确保连接设备已经不是旧的设备否则递归调用该函数
- mConnectedDevice = null;
- updateConnection();
- }
- }
- });
- return;
- }
- if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
- ...
- unadvertiseDisplay();
- mHandler.removeCallbacks(mConnectionTimeout);
- final WifiP2pDevice oldDevice = mConnectingDevice;
- mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() { //在尝试连接到新设备之前,取消正在进行的p2p连接
- @Override
- public void onSuccess() {
- ...
- next();
- }
- @Override
- public void onFailure(int reason) {
- ...
- next();
- }
- private void next() {
- if (mConnectingDevice == oldDevice) {
- mConnectingDevice = null;
- updateConnection();
- }
- }
- });
- return;
- }
- // 如果想断开连接,则任务结束
- if (mDesiredDevice == null) {
- unadvertiseDisplay();
- return;
- }
- if (mConnectedDevice == null && mConnectingDevice == null) {
- Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);
- mConnectingDevice = mDesiredDevice;
- WifiP2pConfig config = new WifiP2pConfig();
- config.deviceAddress = mConnectingDevice.deviceAddress;
- config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT;
- WifiDisplay display = createWifiDisplay(mConnectingDevice);
- advertiseDisplay(display, null, 0, 0, 0);
- final WifiP2pDevice newDevice = mDesiredDevice;
- mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {
- //以特定的配置信息开启P2P连接,如果当前设备不是P2P组的一部分,会建立P2P小组并发起连接请求;如果当前设备是现存P2P组的一部分,则加入该组的邀请会发送至该配对设备。
- @Override
- public void onSuccess() {
- //为了防止连接还没有建立成功,这里设定了等待处理函数,如果在定长时间内还没有接受到WIFI_P2P_CONNECTION_CHANGED_ACTION广播,则按照handleConnectionFailure(true)处理。
- Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);
- mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);
- }
- @Override
- public void onFailure(int reason) {
- if (mConnectingDevice == newDevice) {
- Slog.i(TAG, "Failed to initiate connection to Wifi display: "
- + newDevice.deviceName + ", reason=" + reason);
- mConnectingDevice = null;
- handleConnectionFailure(false);
- }
- }
- });
- return;
- }
- // 根据连接的网络地址和端口号监听Rtsp流连接
- if (mConnectedDevice != null && mRemoteDisplay == null) {
- Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
- if (addr == null) {
- Slog.i(TAG, "Failed to get local interface address for communicating "
- + "with Wifi display: " + mConnectedDevice.deviceName);
- handleConnectionFailure(false);
- return; // done
- }
- setRemoteSubmixOn(true);
- final WifiP2pDevice oldDevice = mConnectedDevice;
- final int port = getPortNumber(mConnectedDevice);
- final String iface = addr.getHostAddress() + ":" + port;
- mRemoteDisplayInterface = iface;
- Slog.i(TAG, "Listening for RTSP connection on " + iface
- + " from Wifi display: " + mConnectedDevice.deviceName);
- mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
- //开始监听连接上的接口
- @Override
- public void onDisplayConnected(Surface surface,
- int width, int height, int flags) {
- if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
- Slog.i(TAG, "Opened RTSP connection with Wifi display: "
- + mConnectedDevice.deviceName);
- mRemoteDisplayConnected = true;
- mHandler.removeCallbacks(mRtspTimeout);
- final WifiDisplay display = createWifiDisplay(mConnectedDevice);
- advertiseDisplay(display, surface, width, height, flags);
- }
- }
- @Override
- public void onDisplayDisconnected() {
- if (mConnectedDevice == oldDevice) {
- Slog.i(TAG, "Closed RTSP connection with Wifi display: "
- + mConnectedDevice.deviceName);
- mHandler.removeCallbacks(mRtspTimeout);
- disconnect();
- }
- }
- @Override
- public void onDisplayError(int error) {
- if (mConnectedDevice == oldDevice) {
- Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
- + error + ": " + mConnectedDevice.deviceName);
- mHandler.removeCallbacks(mRtspTimeout);
- handleConnectionFailure(false);
- }
- }
- }, mHandler);
- mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000);
- }
- }
至此,我们已经了解了建立WifiDisplay连接的基本流程,当然可以继续向底层深入,只要用户选择尝试连接并且已经确认处于连接断开的状态,则会调用WifiP2pManager中的connect()接口函数,该函数会向Channel中发送CONNECT信号,并注册监听器监听相应结果。在进入P2pStateMachine状态机后,WifiP2pService会分为两种情况进行处理。如果当前的设备不是P2P组的成员,WifiP2pService会调用WifiNative类中的p2pConnect()函数,该函数会继续向底层调用,最终会调用wifi.cwifi_send_command()命令,把groupnegotiation请求发送至wpa_supplicant供其处理;如果这个设备已经是P2P组的成员,或者自己通过WifiNative类中的p2pGroupAdd()函数创建了一个组,那么会进入GroupCreatedState,进一步会调用WifiNative类中的p2pInvite()函数向设备发送邀请请求。具体的有关wpa_supplicant同底层驱动的交互,以及wpa_supplicant同WifiMonitor与WifiP2pService状态机之间的调用流程以后有机会再讨论。
在本文的最后,还想继续讨论一下监听RTSP连接的核心实现函数RemoteDisplay.listen(...),
frameworks/base/media/java/android/media/RemoteDisplay.java
- public static RemoteDisplay listen(String iface, Listener listener, Handler handler) {
- ...
- RemoteDisplay display = new RemoteDisplay(listener, handler);
- display.startListening(iface);
- return display;
- }
- 可以看到该监听函数会调用以下函数,并把监听端口作为参数进行传递,
- private void startListening(String iface) {
- mPtr = nativeListen(iface);
- if (mPtr == 0) {
- throw new IllegalStateException("Could not start listening for "
- + "remote display connection on \"" + iface + "\"");
- }
- mGuard.open("dispose");
- }
以上函数最终会调用JNI层的接口函数nativeListen()进行监听。至于CloseGuardmGuard.open(),不理解的话,我们就把它看作是Android提供的一种资源清理机制。
接下来,可以具体看一下RemoteDisplay在JNI层的接口实现,
frameworks/base/core/jni/android_media_RemoteDisplay.cpp
- static jint nativeListen(JNIEnv* env, jobject remoteDisplayObj, jstring ifaceStr) {
- ScopedUtfChars iface(env, ifaceStr); //通过智能指针的方式将string类型转化为只读的UTF chars类型
- sp<IServiceManager> sm = defaultServiceManager();
- sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(
- sm->getService(String16("media.player")));
- //用service manager获得 media player服务的代理实例,即通过interface_cast将其转化成BpMediaPlayerService (Bridge模式)
- if (service == NULL) {
- ALOGE("Could not obtain IMediaPlayerService from service manager");
- return 0;
- }
- sp<NativeRemoteDisplayClient> client(new NativeRemoteDisplayClient(env, remoteDisplayObj));
- sp<IRemoteDisplay> display = service->listenForRemoteDisplay(
- client, String8(iface.c_str()));
- //调用BpMediaPlayerService提供的接口函数,与服务端BnMediaPlayerService进行通讯
- if (display == NULL) {
- ALOGE("Media player service rejected request to listen for remote display '%s'.",
- iface.c_str());
- return 0;
- }
- NativeRemoteDisplay* wrapper = new NativeRemoteDisplay(display, client);
- return reinterpret_cast<jint>(wrapper);
- }
这里采用了Binder通信机制,BpMediaPlayerService继承BpInterface<IMediaPlayerService>作为代理端,采用Bridge模式调用listenForRemoteDisplay()接口函数将上层的监听接口以及实例化的NativeRemoteDisplayClient代理对象传递至服务端BnMediaPlayerService进行处理。
/frameworks/av/media/libmedia/IMediaPlayerService.cpp
- class BpMediaPlayerService: public BpInterface<IMediaPlayerService>
- {
- public:
- …
- virtual sp<IRemoteDisplay> listenForRemoteDisplay(const sp<IRemoteDisplayClient>& client,
- const String8& iface)
- {
- Parcel data, reply;
- data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
- data.writeStrongBinder(client->asBinder());
- data.writeString8(iface);
- remote()->transact(LISTEN_FOR_REMOTE_DISPLAY, data, &reply); //向服务端BnMediaPlayerService发送LISTEN_FOR_REMOTE_DISPLAY 处理命令
- return interface_cast<IRemoteDisplay>(reply.readStrongBinder());
- }
- };
进一步可以看到,NativeRemoteDisplayClient继承于BnRemoteDisplayClient,其实这是IRemoteDisplayClient接口的服务端实现。该类提供了三个接口函数onDisplayConnected()、onDisplayDisconnected()、onDisplayError()是frameworks/base/media/java/android/media/RemoteDisplay.java中RemoteDisplay.Listener{}的三个监听函数在JNI层的实现,特别的,对于onDisplayConnected()函数而言,调用android_view_Surface_createFromISurfaceTexture()函数创建surfaceObj并将其向RemoteDisplay中注册的监听线程传递并进行回调。
frameworks/base/core/jni/android_media_RemoteDisplay.cpp
- virtual void onDisplayConnected(const sp<ISurfaceTexture>& surfaceTexture,
- uint32_t width, uint32_t height, uint32_t flags) {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- jobject surfaceObj = android_view_Surface_createFromISurfaceTexture(env, surfaceTexture);
- //跟据当前获取的media server的surface texture来创建Surface对象
- if (surfaceObj == NULL) {
- ...
- return;
- }
- env->CallVoidMethod(mRemoteDisplayObjGlobal,
- gRemoteDisplayClassInfo.notifyDisplayConnected,
- surfaceObj, width, height, flags); //将Suface对象作为参数传递至notifyDisplayConnected函数用于监听函数的回调
- env->DeleteLocalRef(surfaceObj);
- checkAndClearExceptionFromCallback(env, "notifyDisplayConnected");
- }
接下来,我们继续来看服务端BnMediaPlayerService的实现,其中onTransact函数用于接收来自BpMediaPlayerService发送的命令,如果命令为LISTEN_FOR_REMOTE_DISPLAY,则会读取相应数据并作为参数进行传递。这里的listenForRemoteDisplay()函数是纯虚函数,其实现是由派生类MediaPlayerService来完成的。
- status_t BnMediaPlayerService::onTransact(
- uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
- {
- switch (code) {
- …
- case LISTEN_FOR_REMOTE_DISPLAY: {
- CHECK_INTERFACE(IMediaPlayerService, data, reply);
- sp<IRemoteDisplayClient> client(
- interface_cast<IRemoteDisplayClient>(data.readStrongBinder()));
- String8 iface(data.readString8());
- sp<IRemoteDisplay> display(listenForRemoteDisplay(client, iface));//调用纯虚函数接口,运行时实际调用派生类MediaPlayerService的函数实现
- reply->writeStrongBinder(display->asBinder());
- return NO_ERROR;
- } break;
- default:
- return BBinder::onTransact(code, data, reply, flags);
- }
- }
最后,来看一看该函数的实际实现,
frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp
- sp<IRemoteDisplay> MediaPlayerService::listenForRemoteDisplay(
- const sp<IRemoteDisplayClient>& client, const String8& iface) {
- if (!checkPermission("android.permission.CONTROL_WIFI_DISPLAY")) {
- //检查是否有WIFI Display权限
- return NULL;
- }
- return new RemoteDisplay(client, iface.string()); //直接调用 RemoteDisplay构造函数来开启Wifi display source端
- }
其中,RemoteDisplay继承于BnRemoteDisplay,也采取了Binder通信机制,代理端BpRemoteDisplay与服务端BnRemoteDisplay的接口实现详见frameworks/av/media/libmedia/IRemoteDisplay.cpp。这里,值得一提的是,函数listenForRemoteDisplay()假设在同一时刻连接到指定网络端口iface的remotedisplay设备最多只有一个。换句话说,在同一时刻只有一个设备能作为WifiDisplay source端设备进行播放。
最后,我们来看一看开启Wifidisplay source端的这个构造函数,frameworks/av/media/libmediaplayerservice/RemoteDisplay.cpp
- RemoteDisplay::RemoteDisplay(
- const sp<IRemoteDisplayClient> &client, const char *iface)
- : mLooper(new ALooper),
- mNetSession(new ANetworkSession),
- mSource(new WifiDisplaySource(mNetSession, client)) {
- mLooper->setName("wfd_looper");
- mLooper->registerHandler(mSource); //注册了Wifi display 处理线程
- mNetSession->start(); //初始化数据管道,启动NetworkThread线程,进入threadLoop中监听数据流变化等待处理
- mLooper->start(); //开启消息处理管理线程
- mSource->start(iface); //将网络端口作为消息载体进行传递处理,并等待响应结果,完成与Wifi Display source端开启播放的相关工作
- }
其中mLooper,mNetSession, mSource分别为sp<ALooper>mLooper,sp<ANetworkSession>mNetSession以及sp<WifiDisplaySource>mSource等三个强指针,对强指针概念不清的请见此。此处是利用构造函数的初始化列表将这三个强指针指向这三个new出来的对象。之后便是利用这三个指针,调用类中的方法以开启Wifidisplay source端进行播放。这里,ALooper是关于线程以及消息队列等待处理管理相关的一个类。ANetworkSessions是管理所有与数据报文和数据流相关socket的一个单线程帮助类。在此处,该类负责管理与WifiDisplay播放相关的socket,其中相关的数据传递和消息返回通过AMessage类对象和方法进行。WifiDisplaySource光看命名就知道,其主要负责WifiDisplaysource端的开启关闭,以及与其相关的建立Rtsp服务器,管理所有支持的协议连接、数据流传递以及各个状态之间转换处理等内容。此外,该类还定义了关闭WifiDisplay source端,停止相关线程、关闭socket以及释放资源等内容。
至此,有关WifiDisplay设备连接和建立数据流的流程已经交代清楚了,可以看到应用层建立的连接是与source端相关的。Sink端的主程序在frameworks/av/media/libstagefright/wifi-display/wfd.cpp中,与sink端实现相关的程序在frameworks/av/media/libstagefright/wifi-display/sink目录下面。关于source如何建立rtsp连接,开始通信,各个状态之间的转换以及与sink端的交互将在下回介绍。