天天看點

Android 4.2 Wifi Display 之 Settings 源碼分析(一) - 指針空間

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

2015-10-13 20:24 

指針空間 

閱讀(1359) 

評論(0) 

編輯 

收藏 

舉報

一、簡單背景

      簡單背景:随着無線互聯的深入,不管是藍牙、WIFI或者各種基于此的規範不管是UPNP還是DLNA都随着使用者的需求得到了很大的發展,google 自從android 4.0引入wifi direct後,又在11月份公布的android 4.2中引入了Miracast無線顯示共享,其協定在此可以下載下傳。具體的協定部分内容比較多,本人由于水準有限,就不在這裡羅列協定的内容了,隻附上一份架構圖供大家對其有個大緻的印象。

Android 4.2 Wifi Display 之 Settings 源碼分析(一) - 指針空間

英文縮寫對應如下:

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.         public DemoPresentation(Context context, Display display) {  
  4.             super(context, display);  
  5.         }  
  6.        ...  
  7. }  

為了進一步優化附加顯示裝置自定義示範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.    @Override  
  6.    public void onDisplayChanged(int displayId) {  
  7.        updatePresentationDisplays(displayId);  
  8.    }  
  9.    @Override  
  10.    public void onDisplayRemoved(int displayId) {  
  11.        updatePresentationDisplays(displayId);  
  12.    }  

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.         @Override  
  6.         public void onDisplayRemoved(int displayId) {  
  7.             if (displayId == mDisplay.getDisplayId()) {  
  8.                 handleDisplayRemoved();  
  9.             }  
  10.         }  
  11.         @Override  
  12.         public void onDisplayChanged(int displayId) {  
  13.             if (displayId == mDisplay.getDisplayId()) {  
  14.                 handleDisplayChanged();  
  15.             }  
  16.         }  
  17.     };  

該注冊函數的實作實際是在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.        synchronized (mLock) {  
  6.            int index = findDisplayListenerLocked(listener);  
  7.            if (index < 0) {  
  8.                mDisplayListeners.add(new DisplayListenerDelegate(listener, handler)); //給動态數組中增添顯示監聽處理代理  
  9.                registerCallbackIfNeededLocked();     //實際負責注冊回調函數的方法  
  10.            }  
  11.        }  
  12.    }  

[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. /** @hide */  
  3. interface IDisplayManagerCallback {  
  4.     oneway void onDisplayEvent(int displayId, int event);  
  5. }  

之後則是将建立的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.         synchronized (mSyncRoot) {  
  7.             int callingPid = Binder.getCallingPid();  
  8.             if (mCallbacks.get(callingPid) != null) {  
  9.                 throw new SecurityException("The calling process has already "  
  10.                         + "registered an IDisplayManagerCallback.");  
  11.             }  
  12.             CallbackRecord record = new CallbackRecord(callingPid, callback);  
  13.             try {  
  14.                 IBinder binder = callback.asBinder();  
  15.                 binder.linkToDeath(record, 0);     
  16.             } catch (RemoteException ex) {  
  17.                 // give up  
  18.                 throw new RuntimeException(ex);  
  19.             }  
  20.             mCallbacks.put(callingPid, record);  
  21.         }  
  22.     }  

可以看到該服務采取同步機制,這是因為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.                 case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED:  
  9.                     handleDisplayDeviceChanged(device);  
  10.                     break;  
  11.                 case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED:  
  12.                     handleDisplayDeviceRemoved(device);  
  13.                     break;  
  14.             }  
  15.         }  
  16.       ...  
  17. }  

        這樣将服務與顯示裝置擴充卡分離的做法有兩方面優點,其一方面簡潔的封裝了兩個類的不同職責:顯示擴充卡負責處理各個顯示裝置而顯示管理服務則負責處理全局的狀态變化;另一方面,其将會避免在異步搜尋顯示裝置時導緻的死鎖問題。

         接下來,讓我們進入正題,來通過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.            if (display.equals(mWifiDisplayStatus.getActiveDisplay())) {  
  7.                showDisconnectDialog(display);  
  8.            } else {  
  9.                mDisplayManager.connectWifiDisplay(display.getDeviceAddress());  
  10.            }  
  11.        }  
  12.        return super.onPreferenceTreeClick(preferenceScreen, preference);  
  13.    }  

        最後,該應用還提供對裝置重命名以及剔除Wifi Display 裝置連接配接曆史資訊的方法。

三、Frameworks層分析

  首先,從應用層的裝置發現來往下分析,我們容易看到,如同對上面PresentationWithMediaRouterActivity應用的分析,這裡采取的也是AIDL程序間通信方式。來看一看接口定義檔案,

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

[java] view plaincopy

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

         可以看到,該接口中定義了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. //擷取DISPLAY_SERVICE服務代理并用于填充構造函數  
  8.                 }  
  9.             }  
  10.             return sInstance;  
  11.         }  
  12.     }  
  13. private final IDisplayManager mDm;  
  14.  // AIDL接口對象  
  15. private DisplayManagerGlobal(IDisplayManager dm)  
  16. {   
  17.     mDm = dm;   
  18. }    
  19.  public void scanWifiDisplays() {  
  20.         try {  
  21.             mDm.scanWifiDisplays();  
  22.         } catch (RemoteException ex) {  
  23.             Log.e(TAG, "Failed to scan for Wifi displays.", ex);  
  24.         }  
  25.     }  

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.enable為1或config_enableWifiDisplay為1,并且不是核心模式和安全模式才進行初始化。該服務是displays的全局管理者,決定如何根據目前連結的實體顯示裝置來配置邏輯顯示器。當狀态發生變化時發送通知給系統和應用程式,等等。包括的擴充卡有OverlayDisplayAdapter,WifiDisplayAdapter,HeadlessDisplayAdapter,LocalDisplayAdapter。對于WifiDisplayAdapter而言,流程大緻如下圖所示,

Android 4.2 Wifi Display 之 Settings 源碼分析(一) - 指針空間

接下來讓我們接着之前的流程繼續分析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.         getHandler().post(new Runnable() {  
  6.             @Override  
  7.             public void run() {  
  8.                 if (mDisplayController != null) {  
  9.                     mDisplayController.requestScan();  
  10.                 }  
  11.             }  
  12.         });  
  13.     }  

可以看到此函數又調用了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. private void discoverPeers() {  
  5.         if (!mDiscoverPeersInProgress) {  
  6.             mDiscoverPeersInProgress = true;  
  7.             mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES;  //嘗試發現配對裝置次數,預設值為10  
  8.             handleScanStarted();  
  9.             tryDiscoverPeers();  
  10.         }  
  11. private void handleScanStarted() {  
  12.         mHandler.post(new Runnable() {  
  13.             @Override  
  14.             public void run() {  
  15.                 mListener.onScanStarted();  //供WifiDisplayAdapter使用的監聽器接口函數  
  16.             }  
  17.         });  
  18.     }  
  19. private void tryDiscoverPeers() {  
  20.         mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {  //直接調用  
  21. WifiP2pManager接口  
  22.  @Override   
  23.    public void onSuccess() {   
  24.     ...   
  25.       mDiscoverPeersInProgress = false;   
  26.        requestPeers();     //獲得P2P已經配對的裝置在判斷是否是Wifidisplay裝置,如果是加入WifiP2pDevice動态數組中   
  27.       }   
  28.    @Override   
  29. public void onFailure(int reason) {   
  30.       ...   
  31.       if (mDiscoverPeersInProgress)   
  32.        {   
  33.         if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled)  
  34.            {   
  35.              mHandler.postDelayed(new Runnable()  
  36.               { @Override   
  37.                   public void run()  
  38.                   {   
  39.                     if (mDiscoverPeersInProgress)  
  40.                      {   
  41.                          if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled)  
  42.                         {   
  43.                              mDiscoverPeersRetriesLeft -= 1;  
  44.                                         ...   
  45.                                        tryDiscoverPeers();  
  46.                          }  
  47.                          else {   
  48.                                       handleScanFinished();   
  49.                                mDiscoverPeersInProgress = false;   
  50.                                }   
  51.            }   
  52.        }   
  53.         }, DISCOVER_PEERS_RETRY_DELAY_MILLIS);  //一次發現不成功後延時定長時間後繼續嘗試連接配接,遞歸函數   
  54.           } else {   
  55.                   handleScanFinished();  
  56.                   mDiscoverPeersInProgress = false;  
  57.                        }  
  58.              }   
  59.            }   
  60.             });   
  61.      }  

可以看到,這裡直接調用了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.                 mAvailableWifiDisplayPeers.clear();  
  9.                 for (WifiP2pDevice device : peers.getDeviceList()) {  
  10.                     if (DEBUG) {  
  11.                         Slog.d(TAG, "  " + describeWifiP2pDevice(device));  
  12.                     }  
  13.                if (isWifiDisplay(device)) {    //根據裝置wfdInfo來判斷其是否支援wifi display;并且判斷其裝置類型是否是主sink裝置   
  14.                    mAvailableWifiDisplayPeers.add(device);   
  15.                   }  
  16.               }   
  17.                    handleScanFinished();  //結束裝置發現,對所有符合要求的wifidisplay裝置建立Parcelable對象  
  18.             }  
  19.         });  
  20.     }  

類似與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.         @Override  
  13.         public void onScanStarted() {  
  14.             synchronized (getSyncRoot()) {  
  15.                 if (mScanState != WifiDisplayStatus.SCAN_STATE_SCANNING) {  
  16.                     mScanState = WifiDisplayStatus.SCAN_STATE_SCANNING;  
  17.                     scheduleStatusChangedBroadcastLocked();  
  18.                 }  
  19.             }  
  20.         }  
  21.         @Override  
  22.         public void onScanFinished(WifiDisplay[] availableDisplays) {  
  23.             synchronized (getSyncRoot()) {  
  24.                 availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(  
  25.                         availableDisplays);  
  26.                 if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING  
  27.                         || !Arrays.equals(mAvailableDisplays, availableDisplays)) {  
  28.                     mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;  
  29.                     mAvailableDisplays = availableDisplays;  
  30.                     scheduleStatusChangedBroadcastLocked();  
  31.                 }  
  32.             }  
  33.         }  
  34.               ...  
  35.     };  

可以看到,這些監聽接口函數在觸發時,都會調用同一個函數scheduleStatusChangedBroadcastLocked(),

[java] view plaincopy

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

函數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.            mPendingStatusChangeBroadcast = false;  
  8.            intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);  
  9.            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);  
  10.            intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,  
  11.                    getWifiDisplayStatusLocked());  
  12.        }  
  13.        // Send protected broadcast about wifi display status to registered receivers.  
  14.        getContext().sendBroadcastAsUser(intent, UserHandle.ALL);  
  15.    }  

         最後,我們來看看對于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在裝置發現時的基本調用流程,關于連接配接和開始傳送資料流等内容将會再下一回的内容讨論,謝謝關注!

Android 4.2 Wifi Display 之 Settings 源碼分析(一) - 指針空間