代码改变世界

Android 4.2 Wifi Display 之 Settings 源码分析(一)

2015-10-13 20:24  指针空间  阅读(1679)  评论(0编辑  收藏  举报

一、简单背景

      简单背景:随着无线互联的深入,不管是蓝牙、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应用而言,

 

[java] view plaincopy
 
  1. MediaRouter mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);  
  2.      MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO);  
  3.  Display presentationDisplay = route != null ? route.getPresentationDisplay() : null;  

可以看到这里传入的是ROUTE_TYPE_LIVE_VIDEO类型,供其获取已选择的route信息。之后,则是判断route信息是否为空,如果不为空则返回被选择演示(presentation)设备。值得一提的是,该方法只对 route信息类型为ROUTE_TYPE_LIVE_VIDEO有效。

        接下来,只要将该Display对象作为自己重构的演示(Presentation)类构造函数参数传入,这样自己重构的演示就会出现在第二个显示设备上。

[java] view plaincopy
 
  1.  mPresentation = new DemoPresentation(this, presentationDisplay);  
  2.     ...  
  3.  try {  
  4.                 mPresentation.show();  
  5.             } catch (WindowManager.InvalidDisplayException ex) {  
  6.                 Log.w(TAG, "Couldn't show presentation!  Display was removed in "  
  7.                         + "the meantime.", ex);  
  8.                 mPresentation = null;  
  9.             }  
  10.         }  
  11. ...  

 

 

[java] view plaincopy
 
  1. private final static class DemoPresentation extends Presentation {  
  2.         ...  
  3.   
  4.         public DemoPresentation(Context context, Display display) {  
  5.             super(context, display);  
  6.         }  
  7.        ...  
  8. }  

 

为了进一步优化附加显示设备自定义演示UI的显示效果,你可以在<style>属性中指定相关应用主题为android:presentationTheme。

   为了在运行时检测外设显示设备的连接状态,你需要在自己的实现类中创建一个 MediaRouter.SimpleCallback的一个实例,该实例中需要自己实现onRoutePresentationDisplayChanged() 等回调函数。当一个新的演示显示设备连接时,系统就会回调该函数,进一步其就会调用上面提到的MediaRouter.getSelectedRoute()函数。

 

[java] view plaincopy
 
  1. private final MediaRouter.SimpleCallback mMediaRouterCallback =  
  2.            new MediaRouter.SimpleCallback() {  
  3.        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {  
  4.            updatePresentation();  
  5.        }  
  6.        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {  
  7.            updatePresentation();  
  8.        }  
  9.        public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) {  
  10.            updatePresentation();  
  11.        }  
  12.    };  



 

当然,使用者需要使用MediaRouter.addCallback()函数完成注册,如同在PresentationWithMediaRouterActivity应用中,

 

[java] view plaincopy
 
  1. mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_LIVE_VIDEO, mMediaRouterCallback);  

 

这里可以简单看看调用流程,首先可以看到onRoutePresentationDisplayChanged()回调函数在MediaRouter.java会先触发dispatchRoutePresentationDisplayChanged()函数,

frameworks/base/media/java/android/media/MediaRouter.java

 

[java] view plaincopy
 
  1. static void dispatchRoutePresentationDisplayChanged(RouteInfo info) {  
  2.     for (CallbackInfo cbi : sStatic.mCallbacks) {  
  3.         if ((cbi.type & info.mSupportedTypes) != 0) {  
  4.             cbi.cb.onRoutePresentationDisplayChanged(cbi.router, info);  
  5.         }  
  6.     }  
  7. }  

 

进一步可以看到该分发函数被用于更新演示设备的状态,并将其提供给frameworks/base/core/java/android/app/Presentation.java中注册的监听函数,

frameworks/base/media/java/android/media/MediaRouter.java

 

[java] view plaincopy
 
  1. private void updatePresentationDisplays(int changedDisplayId) {  
  2.             final Display[] displays = getAllPresentationDisplays();  
  3.             final int count = mRoutes.size();  
  4.             for (int i = 0; i < count; i++) {  
  5.                 final RouteInfo info = mRoutes.get(i);  
  6.                 Display display = choosePresentationDisplayForRoute(info, displays);  //根据displays的地址信息从所有显示类型为Presentation displays的设备中选择对应的显示设备  
  7.                 if (display != info.mPresentationDisplay  
  8.                         || (display != null && display.getDisplayId() == changedDisplayId)) {  
  9.                     info.mPresentationDisplay = display;  
  10.                     dispatchRoutePresentationDisplayChanged(info);  
  11.                 }  
  12.             }  
  13.         }  

 

 

[java] view plaincopy
 
  1.   @Override  
  2.    public void onDisplayAdded(int displayId) {  
  3.        updatePresentationDisplays(displayId);  
  4.    }  
  5.   
  6.    @Override  
  7.    public void onDisplayChanged(int displayId) {  
  8.        updatePresentationDisplays(displayId);  
  9.    }  
  10.   
  11.    @Override  
  12.    public void onDisplayRemoved(int displayId) {  
  13.        updatePresentationDisplays(displayId);  
  14.    }  

 

frameworks/base/core/java/android/app/Presentation.java

 

[java] view plaincopy
 
  1.  @Override  
  2.     protected void onStart() {  
  3.         super.onStart();  
  4.         mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);// Presentation线程一启动就会注册Display Manager中负责监听演示设备变化的三个监听器  
  5.         ...  
  6.     }  

 

[java] view plaincopy
 
  1. private final DisplayListener mDisplayListener = new DisplayListener() {  
  2.         @Override  
  3.         public void onDisplayAdded(int displayId) {  
  4.         }  
  5.   
  6.         @Override  
  7.         public void onDisplayRemoved(int displayId) {  
  8.             if (displayId == mDisplay.getDisplayId()) {  
  9.                 handleDisplayRemoved();  
  10.             }  
  11.         }  
  12.   
  13.         @Override  
  14.         public void onDisplayChanged(int displayId) {  
  15.             if (displayId == mDisplay.getDisplayId()) {  
  16.                 handleDisplayChanged();  
  17.             }  
  18.         }  
  19.     };  

该注册函数的实现实际是在DisplayManagerGlobal类中,该类主要负责管理显示管理器(Display Manager)与显示管理服务(Display Manager Service)之间的通信。

 

frameworks/base/core/java/android/hardware/display/DisplayManagerGlobal.java

 

[java] view plaincopy
 
  1. public void registerDisplayListener(DisplayListener listener, Handler handler) {  
  2.        if (listener == null) {  
  3.            throw new IllegalArgumentException("listener must not be null");  
  4.        }  
  5.   
  6.        synchronized (mLock) {  
  7.            int index = findDisplayListenerLocked(listener);  
  8.            if (index < 0) {  
  9.                mDisplayListeners.add(new DisplayListenerDelegate(listener, handler)); //给动态数组中增添显示监听处理代理  
  10.                registerCallbackIfNeededLocked();     //实际负责注册回调函数的方法  
  11.            }  
  12.        }  
  13.    }  

 

[java] view plaincopy
 
  1. private void registerCallbackIfNeededLocked() {  
  2.     if (mCallback == null) {  
  3.         mCallback = new DisplayManagerCallback();  
  4.         try {  
  5.             mDm.registerCallback(mCallback);  
  6.         } catch (RemoteException ex) {  
  7.             Log.e(TAG, "Failed to register callback with display manager service.", ex);  
  8.             mCallback = null;  
  9.         }  
  10.     }  
  11. }  

 

可以看到,registerCallbackIfNeededLocked()函数中新建的回调函数实际上是IDisplayManagerCallback的AIDL接口实现,

 

[java] view plaincopy
 
  1. private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {  
  2.         @Override  
  3.         public void onDisplayEvent(int displayId, int event) {  
  4.             if (DEBUG) {  
  5.                 Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event);  
  6.             }  
  7.             handleDisplayEvent(displayId, event);  
  8.         }  
  9.     }  

frameworks/base/core/java/android/hardware/display/IDisplayManagerCallback.aidl

 

 

[java] view plaincopy
 
  1. package android.hardware.display;  
  2.   
  3. /** @hide */  
  4. interface IDisplayManagerCallback {  
  5.     oneway void onDisplayEvent(int displayId, int event);  
  6. }  

 

之后则是将新建的mCallback作为参数传入IDisplayManager中的registerCallback(in IDisplayManagerCallback callback); 接口函数中。

最后,来看看与IDisplayManager AIDL接口对应的Service实现。

 

frameworks/base/services/java/com/android/server/display/DisplayManagerService.java

 

[java] view plaincopy
 
  1.     @Override // Binder call  
  2.     public void registerCallback(IDisplayManagerCallback callback) {  
  3.         if (callback == null) {  
  4.             throw new IllegalArgumentException("listener must not be null");  
  5.         }  
  6.   
  7.         synchronized (mSyncRoot) {  
  8.             int callingPid = Binder.getCallingPid();  
  9.             if (mCallbacks.get(callingPid) != null) {  
  10.                 throw new SecurityException("The calling process has already "  
  11.                         + "registered an IDisplayManagerCallback.");  
  12.             }  
  13.   
  14.             CallbackRecord record = new CallbackRecord(callingPid, callback);  
  15.             try {  
  16.                 IBinder binder = callback.asBinder();  
  17.                 binder.linkToDeath(record, 0);     
  18.             } catch (RemoteException ex) {  
  19.                 // give up  
  20.                 throw new RuntimeException(ex);  
  21.             }  
  22.   
  23.             mCallbacks.put(callingPid, record);  
  24.         }  
  25.     }  


可以看到该服务采取同步机制,这是因为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。

 

[java] view plaincopy
 
  1. private final class DisplayAdapterListener implements DisplayAdapter.Listener {  
  2.         @Override  
  3.         public void onDisplayDeviceEvent(DisplayDevice device, int event) {  
  4.             switch (event) {  
  5.                 case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED:  
  6.                     handleDisplayDeviceAdded(device);  
  7.                     break;  
  8.   
  9.                 case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED:  
  10.                     handleDisplayDeviceChanged(device);  
  11.                     break;  
  12.   
  13.                 case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED:  
  14.                     handleDisplayDeviceRemoved(device);  
  15.                     break;  
  16.             }  
  17.         }  
  18.       ...  
  19.   
  20. }  


        这样将服务与显示设备适配器分离的做法有两方面优点,其一方面简洁的封装了两个类的不同职责:显示适配器负责处理各个显示设备而显示管理服务则负责处理全局的状态变化;另一方面,其将会避免在异步搜索显示设备时导致的死锁问题。

         接下来,让我们进入正题,来通过WiFi Display Setting应用来大致分析一下Wifidisplay具体的流程,其在源码目录下

packages/apps/Settings/src/com/android/settings/wfd/WifiDisplaySettings.java

     关于此应用的细节这里就不再详述,我们首先来看Wifi Display 的设备发现,这里首先需要查看Wifi Display的设备状态,通过调用getFeatureState()可以获得进行该操作的设备是否可以支持Wifi Display,以及该功能是否被用户使能等信息。如果此时的设备状态表示Widfi display功能已经开启,那么就开始进行设备发现

 

[java] view plaincopy
 
  1. if (mWifiDisplayStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {  
  2.                     mDisplayManager.scanWifiDisplays();  
  3.                 }  

      在搜寻完设备后,用户可以选择设备进行连接,当然正在进行连接或已经连接配对的设备,再次点击配置后,会弹出对话框供用户选择断开连接。

 

[java] view plaincopy
 
  1. public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,  
  2.            Preference preference) {  
  3.        if (preference instanceof WifiDisplayPreference) {  
  4.            WifiDisplayPreference p = (WifiDisplayPreference)preference;  
  5.            WifiDisplay display = p.getDisplay();  
  6.   
  7.            if (display.equals(mWifiDisplayStatus.getActiveDisplay())) {  
  8.                showDisconnectDialog(display);  
  9.            } else {  
  10.                mDisplayManager.connectWifiDisplay(display.getDeviceAddress());  
  11.            }  
  12.        }  
  13.   
  14.        return super.onPreferenceTreeClick(preferenceScreen, preference);  
  15.    }  

        最后,该应用还提供对设备重命名以及剔除Wifi Display 设备连接历史信息的方法。

三、Frameworks层分析

  首先,从应用层的设备发现来往下分析,我们容易看到,如同对上面PresentationWithMediaRouterActivity应用的分析,这里采取的也是AIDL进程间通信方式。来看一看接口定义文件,

 

frameworks/base/core/java/android/hardware/display/IDisplayManager.aidl

[java] view plaincopy
 
  1. package android.hardware.display;  
  2.   
  3. import android.hardware.display.IDisplayManagerCallback;  
  4. import android.hardware.display.WifiDisplay;  
  5. import android.hardware.display.WifiDisplayStatus;  
  6. import android.view.DisplayInfo;  
  7.   
  8. /** @hide */  
  9. interface IDisplayManager {  
  10.     DisplayInfo getDisplayInfo(int displayId);  
  11.     int[] getDisplayIds();  
  12.   
  13.     void registerCallback(in IDisplayManagerCallback callback);  
  14.   
  15.     // No permissions required.  
  16.     void scanWifiDisplays();  
  17.   
  18.     // Requires CONFIGURE_WIFI_DISPLAY permission to connect to an unknown device.  
  19.     // No permissions required to connect to a known device.  
  20.     void connectWifiDisplay(String address);  
  21.   
  22.     // No permissions required.  
  23.     void disconnectWifiDisplay();  
  24.   
  25.     // Requires CONFIGURE_WIFI_DISPLAY permission.  
  26.     void renameWifiDisplay(String address, String alias);  
  27.   
  28.     // Requires CONFIGURE_WIFI_DISPLAY permission.  
  29.     void forgetWifiDisplay(String address);  
  30.   
  31.     // No permissions required.  
  32.     WifiDisplayStatus getWifiDisplayStatus();  
  33. }  

         可以看到,该接口中定义了DisplayManger所需要交互的全部函数,包括设备发现和设备连接等函数。DisplayManager是根据DisplayManagerGlobal提供的单实例来访问相应的接口函数,并与Display manager service建立起联系。以下是DisplayManagerGlobal提供的获取其单例模式的函数,

 

frameworks/base/core/java/android/hardware/display/DisplayManagerGlobal.java

 

[java] view plaincopy
 
  1.  public static DisplayManagerGlobal getInstance() {    
  2.         synchronized (DisplayManagerGlobal.class) {  
  3.             if (sInstance == null) {  
  4.                 IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);  
  5.                 if (b != null) {  
  6.              sInstance = new DisplayManagerGlobal(IDisplayManager.Stub.asInterface(b));  
  7.   
  8. //获取DISPLAY_SERVICE服务代理并用于填充构造函数  
  9.                 }  
  10.             }  
  11.             return sInstance;  
  12.         }  
  13.     }  
  14.   
  15. private final IDisplayManager mDm;  
  16.   
  17.  // AIDL接口对象  
  18.   
  19. private DisplayManagerGlobal(IDisplayManager dm)  
  20. {   
  21.     mDm = dm;   
  22. }    
  23.  public void scanWifiDisplays() {  
  24.         try {  
  25.             mDm.scanWifiDisplays();  
  26.         } catch (RemoteException ex) {  
  27.             Log.e(TAG, "Failed to scan for Wifi displays.", ex);  
  28.         }  
  29.     }  
  30.    

 

frameworks/base/core/java/android/hardware/display/DisplayManager.java

 

[java] view plaincopy
 
  1. public DisplayManager(Context context) {  
  2.         mContext = context;  
  3.         mGlobal = DisplayManagerGlobal.getInstance();  
  4.     }  
  5.   public void scanWifiDisplays() {  
  6.         mGlobal.scanWifiDisplays();  
  7. }  

        接下来,再看一看scanWifiDisplays()在Display Manager Service中的实现,也就是AIDL接口的实际实现,

 

frameworks/base/services/java/com/android/server/display/DisplayManagerService.java

 

[java] view plaincopy
 
  1. public final class DisplayManagerService extends IDisplayManager.Stub {  
  2.    ...  
  3.  @Override // Binder call  
  4.     public void scanWifiDisplays() {  
  5.         final long token = Binder.clearCallingIdentity();  
  6.         try {  
  7.             synchronized (mSyncRoot) {  
  8.                 if (mWifiDisplayAdapter != null) {  
  9.                     mWifiDisplayAdapter.requestScanLocked();  
  10.                 }  
  11.             }  
  12.         } finally {  
  13.             Binder.restoreCallingIdentity(token);  
  14.         }  
  15.     }  
  16.   ...  
  17. }  

以上程序使用了mWifiDisplayAdapter对象,WifiDisplayAdapter类继承于显示适配器(DisplayAdapter),该类负责处理完成在连接到Wifi display设备时与媒体服务、Surface Flinger以及显示管理服务之间的各种交互及操作。在继续分析WifiDisplayAdapter中的流程前,我们先来看看系统是启动该服务的大致流程,

首先在ServerThread.run中通过addService(Context.DISPLAY_SERVICE,…)来注册该服务

 

frameworks/base/services/java/com/android/server/SystemServer.java

 

[java] view plaincopy
 
  1. display = new DisplayManagerService(context, wmHandler, uiHandler);  
  2.             ServiceManager.addService(Context.DISPLAY_SERVICE, display, true);  

通过display.systemReady(safeMode,onlyCore)来初始化。

当系统属性persist.debug.wfd.enable1config_enableWifiDisplay1,并且不是内核模式和安全模式才进行初始化。该服务是displays的全局管理者,决定如何根据当前链接的物理显示设备来配置逻辑显示器。当状态发生变化时发送通知给系统和应用程序,等等。包括的适配器有OverlayDisplayAdapter,WifiDisplayAdapter,HeadlessDisplayAdapter,LocalDisplayAdapter。对于WifiDisplayAdapter而言,流程大致如下图所示,

 

接下来让我们接着之前的流程继续分析WifiDisplayAdapter中的发现设备额的调用流程

frameworks/base/services/java/com/android/server/display/WifiDisplayAdapter.java

 

 

[java] view plaincopy
 
  1. public void requestScanLocked() {  
  2.         if (DEBUG) {  
  3.             Slog.d(TAG, "requestScanLocked");  
  4.         }  
  5.   
  6.         getHandler().post(new Runnable() {  
  7.             @Override  
  8.             public void run() {  
  9.                 if (mDisplayController != null) {  
  10.                     mDisplayController.requestScan();  
  11.                 }  
  12.             }  
  13.         });  
  14.     }  

可以看到此函数又调用了WifiDisplayController类中的requestScan()方法,值得注意的是WifiDisplayController对象必须在handler线程中实例化,该类负责处理控制在WifiDisplayAdapter与WifiP2pManager之间的各种异步操作。

frameworks/base/services/java/com/android/server/display/WifiDisplayController.java

 

[java] view plaincopy
 
  1. public void requestScan() {  
  2.         discoverPeers();  
  3.     }  
  4.   
  5. private void discoverPeers() {  
  6.         if (!mDiscoverPeersInProgress) {  
  7.             mDiscoverPeersInProgress = true;  
  8.             mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES;  //尝试发现配对设备次数,默认值为10  
  9.             handleScanStarted();  
  10.             tryDiscoverPeers();  
  11.         }  
  12. private void handleScanStarted() {  
  13.         mHandler.post(new Runnable() {  
  14.             @Override  
  15.             public void run() {  
  16.                 mListener.onScanStarted();  //供WifiDisplayAdapter使用的监听器接口函数  
  17.             }  
  18.         });  
  19.     }  
  20.   
  21. private void tryDiscoverPeers() {  
  22.         mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {  //直接调用  
  23.   
  24. WifiP2pManager接口  
  25.   
  26.  @Override   
  27.    public void onSuccess() {   
  28.     ...   
  29.       mDiscoverPeersInProgress = false;   
  30.        requestPeers();     //获得P2P已经配对的设备在判断是否是Wifidisplay设备,如果是加入WifiP2pDevice动态数组中   
  31.       }   
  32.    @Override   
  33. public void onFailure(int reason) {   
  34.       ...   
  35.       if (mDiscoverPeersInProgress)   
  36.        {   
  37.         if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled)  
  38.            {   
  39.              mHandler.postDelayed(new Runnable()  
  40.               { @Override   
  41.                   public void run()  
  42.                   {   
  43.                     if (mDiscoverPeersInProgress)  
  44.                      {   
  45.                          if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled)  
  46.                         {   
  47.                              mDiscoverPeersRetriesLeft -= 1;  
  48.                                         ...   
  49.                                        tryDiscoverPeers();  
  50.                          }  
  51.                          else {   
  52.                                       handleScanFinished();   
  53.                                mDiscoverPeersInProgress = false;   
  54.                                }   
  55.            }   
  56.        }   
  57.         }, DISCOVER_PEERS_RETRY_DELAY_MILLIS);  //一次发现不成功后延时定长时间后继续尝试连接,递归函数   
  58.           } else {   
  59.                   handleScanFinished();  
  60.                   mDiscoverPeersInProgress = false;  
  61.                        }  
  62.              }   
  63.            }   
  64.             });   
  65.      }  

可以看到,这里直接调用了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()完成

 

[java] view plaincopy
 
  1.  private void requestPeers() {  
  2.         mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() {  
  3.             @Override  
  4.             public void onPeersAvailable(WifiP2pDeviceList peers) {  
  5.                 if (DEBUG) {  
  6.                     Slog.d(TAG, "Received list of peers.");  
  7.                 }  
  8.   
  9.                 mAvailableWifiDisplayPeers.clear();  
  10.                 for (WifiP2pDevice device : peers.getDeviceList()) {  
  11.                     if (DEBUG) {  
  12.                         Slog.d(TAG, "  " + describeWifiP2pDevice(device));  
  13.                     }  
  14.                if (isWifiDisplay(device)) {    //根据设备wfdInfo来判断其是否支持wifi display;并且判断其设备类型是否是主sink设备   
  15.                    mAvailableWifiDisplayPeers.add(device);   
  16.                   }  
  17.               }   
  18.                    handleScanFinished();  //结束设备发现,对所有符合要求的wifidisplay设备创建Parcelable对象  
  19.             }  
  20.         });  
  21.     }  

类似与handleScanStarted()函数,这里结束设备发现任务并且完成相应处理工作的函数handleScanFinished(),也开启监听线程。这些监听线程将在WifiDisplayAdapter被注册使用,

frameworks/base/services/java/com/android/server/display/WifiDisplayAdapter.java

 

[java] view plaincopy
 
  1. private final WifiDisplayController.Listener mWifiDisplayListener =  
  2.             new WifiDisplayController.Listener() {  
  3.         @Override  
  4.         public void onFeatureStateChanged(int featureState) {  
  5.             synchronized (getSyncRoot()) {  
  6.                 if (mFeatureState != featureState) {  
  7.                     mFeatureState = featureState;  
  8.                     scheduleStatusChangedBroadcastLocked();  
  9.                 }  
  10.             }  
  11.         }  
  12.   
  13.         @Override  
  14.         public void onScanStarted() {  
  15.             synchronized (getSyncRoot()) {  
  16.                 if (mScanState != WifiDisplayStatus.SCAN_STATE_SCANNING) {  
  17.                     mScanState = WifiDisplayStatus.SCAN_STATE_SCANNING;  
  18.                     scheduleStatusChangedBroadcastLocked();  
  19.                 }  
  20.             }  
  21.         }  
  22.   
  23.         @Override  
  24.         public void onScanFinished(WifiDisplay[] availableDisplays) {  
  25.             synchronized (getSyncRoot()) {  
  26.                 availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(  
  27.                         availableDisplays);  
  28.   
  29.                 if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING  
  30.                         || !Arrays.equals(mAvailableDisplays, availableDisplays)) {  
  31.                     mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;  
  32.                     mAvailableDisplays = availableDisplays;  
  33.                     scheduleStatusChangedBroadcastLocked();  
  34.                 }  
  35.             }  
  36.         }  
  37.               ...  
  38.     };  

可以看到,这些监听接口函数在触发时,都会调用同一个函数scheduleStatusChangedBroadcastLocked(),

 

[java] view plaincopy
 
  1. private final WifiDisplayHandler mHandler;  
  2.    
  3. private void scheduleStatusChangedBroadcastLocked() {  
  4.         mCurrentStatus = null;  
  5.         if (!mPendingStatusChangeBroadcast) {  
  6.             mPendingStatusChangeBroadcast = true;  
  7.             mHandler.sendEmptyMessage(MSG_SEND_STATUS_CHANGE_BROADCAST);  
  8.         }  
  9.     }  
  10.  private final class WifiDisplayHandler extends Handler {  
  11.         public WifiDisplayHandler(Looper looper) {  
  12.             super(looper, nulltrue /*async*/);  
  13.         }  
  14.   
  15.         @Override  
  16.         public void handleMessage(Message msg) {  
  17.             switch (msg.what) {  
  18.                 case MSG_SEND_STATUS_CHANGE_BROADCAST:  
  19.                     handleSendStatusChangeBroadcast();  
  20.                     break;  
  21.                 ...  
  22.             }  
  23.         }  
  24.     }  
  25. }  

函数scheduleStatusChangedBroadcastLocked()会向内类注册的Handler处理函数发送MSG_SEND_STATUS_CHANGE_BROADCAST消息,处理函数接收到该消息后由handleSendStatusChangeBroadcast()向设备上所有注册过ACTION_WIFI_DISPLAY_STATUS_CHANGED这一intent的接受者发送WifiDisplayStatus广播,

 

[java] view plaincopy
 
  1. private void handleSendStatusChangeBroadcast() {  
  2.        final Intent intent;  
  3.        synchronized (getSyncRoot()) {  
  4.            if (!mPendingStatusChangeBroadcast) {  
  5.                return;  
  6.            }  
  7.   
  8.            mPendingStatusChangeBroadcast = false;  
  9.            intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);  
  10.            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);  
  11.            intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,  
  12.                    getWifiDisplayStatusLocked());  
  13.        }  
  14.   
  15.        // Send protected broadcast about wifi display status to registered receivers.  
  16.        getContext().sendBroadcastAsUser(intent, UserHandle.ALL);  
  17.    }  

         最后,我们来看看对于Wifi Display 设备发现最后需要注意的一个部分,即在WifidisplayController中调用的WifiP2pManager中的discoverPeers()接口函数,

frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pManager.java

 

[java] view plaincopy
 
  1. public void discoverPeers(Channel c, ActionListener listener) {  
  2.        checkChannel(c);  
  3.        c.mAsyncChannel.sendMessage(DISCOVER_PEERS, 0, c.putListener(listener));  
  4.    }  

当用户在搜寻设备时,该函数会向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

 

[java] view plaincopy
 
  1. clearSupplicantServiceRequest();  
  2.                  if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {  
  3.                      replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED);  
  4.                      sendP2pDiscoveryChangedBroadcast(true);  
  5.                  } else {  
  6.                      replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,  
  7.                              WifiP2pManager.ERROR);  
  8.                  }  

 

        如果p2pFind(int timeout)调用doBooleanCommand("P2P_FIND " + timeout)并且执行成功,则向先前连接的P2pHandler处理函数回复DISCOVER_PEERS_SUCCEEDED信号,并且调用监听函数回调接口((ActionListener) listener).onSuccess(),回调WifidisplayController中的discoverPeers()函数做发现设备成功后的获得设备列表工作即执行函数requestPeers()。最后,还会给在boot之前注册的接收者发送WIFI_P2P_DISCOVERY_CHANGED_ACTION广播。

         至此,本文已经基本讲清楚了Wifi Display在设备发现时的基本调用流程,关于连接和开始传送数据流等内容将会再下一回的内容讨论,谢谢关注!