Android WifiDisplay分析一:相關Service的啓動

本文轉載自:李煉  Android WifiDisplay分析一:相關Service的啓動

原文地址:http://blog.csdn.net/lilian0118/article/details/22849249

最近在學習Android 4.4上面的WifiDisplay(Miracast)相關的模塊,這裏先從WifiDisplay用到的各個Service講起,然後再從WifiDisplaySettings裏面講解打開wfd的流程。首先看下面的主要幾個Service的架構圖:


相關Service的啓動

圖中主要有以下幾個模塊,DisplayManagerService、MediaRouterService、WifiDisplayAdapter和WifiDisplayController。其中:

DisplayManagerService用於管理系統顯示設備的生命週期,包含物理屏幕、虛擬屏幕、wifi display等,它用一組DiaplayAdapter來管理這些顯示設備。

MediaRouterService用於管理各個應用程序的多媒體播放的行爲。

MediaRouter用於和MediaRouterService交互一起管理多媒體的播放行爲,並維護當前已經配對上的remote display設備,包括Wifi diplay、藍牙A2DP設備、chromecast設備。

WifiDisplayAdapter是用於DisplayManagerService管理Wifi display顯示的adapter。

WifiDisplayController用於控制掃描wifi display設備、連接、斷開等操作。


先來順着上面的架構圖看各個Service的啓動。首先來看DisplayManagerService,在SystemServer中先創建一個DisplayManagerService對象,然後調用systemReady方法:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public DisplayManagerService(Context context, Handler mainHandler) {  
  2.     mContext = context;  
  3.     mHeadless = SystemProperties.get(SYSTEM_HEADLESS).equals("1");  
  4.   
  5.     mHandler = new DisplayManagerHandler(mainHandler.getLooper());  
  6.     mUiHandler = UiThread.getHandler();  
  7.     mDisplayAdapterListener = new DisplayAdapterListener();  
  8.     mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay"false);  
  9.   
  10.     mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER);  
  11. }  
  12.   
  13. public void systemReady(boolean safeMode, boolean onlyCore) {  
  14.     synchronized (mSyncRoot) {  
  15.         mSafeMode = safeMode;  
  16.         mOnlyCore = onlyCore;  
  17.     }  
  18.   
  19.     mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);  
  20. }  

在DisplayManagerService的構造函數中,首先獲取SYSTEM_HEADLESS屬性,用於表明系統是否支持headless模式,默認爲0。然後創建一個DisplayManagerHandler用於處理DisplayManagerService中的消息,mSigleDisplayDemoMode用於開發模式中。然後給自己發送MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER,我們到DisplayManagerHandler看如何處理這個消息:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private final class DisplayManagerHandler extends Handler {  
  2.     public DisplayManagerHandler(Looper looper) {  
  3.         super(looper, nulltrue /*async*/);  
  4.     }  
  5.   
  6.     @Override  
  7.     public void handleMessage(Message msg) {  
  8.         switch (msg.what) {  
  9.             case MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER:  
  10.                 registerDefaultDisplayAdapter();  
  11.                 break;  
  12.   
  13.             case MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS:  
  14.                 registerAdditionalDisplayAdapters();  
  15.                 break;  

處理MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER消息就是調用registerDefaultDisplayAdapter來註冊一個默認的DiaplayAdapter,DisplayManagerService維護一組DiaplayAdapter,用於管理這些顯示設備。默認的DiaplayAdapter就是系統的物理屏幕,通過Surface flinger來控制輸出。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void registerDefaultDisplayAdapter() {  
  2.     // Register default display adapter.  
  3.     synchronized (mSyncRoot) {  
  4.         if (mHeadless) {  
  5.             registerDisplayAdapterLocked(new HeadlessDisplayAdapter(  
  6.                     mSyncRoot, mContext, mHandler, mDisplayAdapterListener));  
  7.         } else {  
  8.             registerDisplayAdapterLocked(new LocalDisplayAdapter(  
  9.                     mSyncRoot, mContext, mHandler, mDisplayAdapterListener));  
  10.         }  
  11.     }  
  12. }  
  13.   
  14. private void registerDisplayAdapterLocked(DisplayAdapter adapter) {  
  15.     mDisplayAdapters.add(adapter);  
  16.     adapter.registerLocked();  
  17. }  

管理surface finger的知識就不講解了。接着來看systemReady函數中會發送MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS,這裏就會調用registerAdditionalDisplayAdapters來註冊其它的顯示設備:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void registerAdditionalDisplayAdapters() {  
  2.     synchronized (mSyncRoot) {  
  3.         if (shouldRegisterNonEssentialDisplayAdaptersLocked()) {  
  4.             registerOverlayDisplayAdapterLocked();  
  5.             registerWifiDisplayAdapterLocked();  
  6.             registerVirtualDisplayAdapterLocked();  
  7.         }  
  8.     }  
  9. }  

這裏主要註冊三種DisplayAdapter,一種是OverlayDiaplayAdapter用於開發模式用;一種是WifiDisplayAdapter用於wifi display,也是我們接下來要講的;還有一種是虛擬顯示。接下來只看registerWifiDisplayAdapterLocked:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void registerWifiDisplayAdapterLocked() {  
  2.     if (mContext.getResources().getBoolean(  
  3.             com.android.internal.R.bool.config_enableWifiDisplay)  
  4.             || SystemProperties.getInt(FORCE_WIFI_DISPLAY_ENABLE, -1) == 1) {  
  5.         mWifiDisplayAdapter = new WifiDisplayAdapter(  
  6.                 mSyncRoot, mContext, mHandler, mDisplayAdapterListener,  
  7.                 mPersistentDataStore);  
  8.         registerDisplayAdapterLocked(mWifiDisplayAdapter);  
  9.     }  
  10. }  

這裏會創建WifiDisplayAdapter對象,我們到它的構造函數中去分析,並調用registerDisplayAdapterLocked添加到mDisplayAdapter中,這裏會回調WifiDisplayAdapter的registerLocked方法:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,  
  2.         Context context, Handler handler, Listener listener,  
  3.         PersistentDataStore persistentDataStore) {  
  4.     super(syncRoot, context, handler, listener, TAG);  
  5.     mHandler = new WifiDisplayHandler(handler.getLooper());  
  6.     mPersistentDataStore = persistentDataStore;  
  7.     mSupportsProtectedBuffers = context.getResources().getBoolean(  
  8.             com.android.internal.R.bool.config_wifiDisplaySupportsProtectedBuffers);  
  9.     mNotificationManager = (NotificationManager)context.getSystemService(  
  10.             Context.NOTIFICATION_SERVICE);  
  11. }  
  12.   
  13. public void registerLocked() {  
  14.     super.registerLocked();  
  15.   
  16.     updateRememberedDisplaysLocked();  
  17.   
  18.     getHandler().post(new Runnable() {  
  19.         @Override  
  20.         public void run() {  
  21.             mDisplayController = new WifiDisplayController(  
  22.                     getContext(), getHandler(), mWifiDisplayListener);  
  23.   
  24.             getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,  
  25.                     new IntentFilter(ACTION_DISCONNECT), null, mHandler);  
  26.         }  
  27.     });  
  28. }  

PersistentDateStore用於持久性存儲連過的wifi display設備,用於在WifiDisplaySettings中顯示前面已經連接過的設備列表。SupportsProtectedBuffer與gralloc顯示相關。在registerLocked通過updateRememberedDisplaysLocked去加載/data/system/display-manager-state.xml中保存過的列表,並記錄在mRememberedDisplays中。接着實例化一個WifiDisplayController對象,同時註冊對ACTION_DISCONNECT的receiver。接着到WifiDisplayController去分析,注意WifiDisplayController最後一個參數用於回調通知WifiDisplayAdapter相關狀態的改變,比如wifi display打開/關閉、wifi display連接/斷開等。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public WifiDisplayController(Context context, Handler handler, Listener listener) {  
  2.     mContext = context;  
  3.     mHandler = handler;  
  4.     mListener = listener;  
  5.   
  6.     mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);  
  7.     mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null);  
  8.   
  9.     IntentFilter intentFilter = new IntentFilter();  
  10.     intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);  
  11.     intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);  
  12.     intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);  
  13.     intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);  
  14.     context.registerReceiver(mWifiP2pReceiver, intentFilter, null, mHandler);  
  15.   
  16.     ContentObserver settingsObserver = new ContentObserver(mHandler) {  
  17.         @Override  
  18.         public void onChange(boolean selfChange, Uri uri) {  
  19.             updateSettings();  
  20.         }  
  21.     };  
  22.   
  23.     final ContentResolver resolver = mContext.getContentResolver();  
  24.     resolver.registerContentObserver(Settings.Global.getUriFor(  
  25.             Settings.Global.WIFI_DISPLAY_ON), false, settingsObserver);  
  26.     resolver.registerContentObserver(Settings.Global.getUriFor(  
  27.             Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON), false, settingsObserver);  
  28.     resolver.registerContentObserver(Settings.Global.getUriFor(  
  29.             Settings.Global.WIFI_DISPLAY_WPS_CONFIG), false, settingsObserver);  
  30.     updateSettings();  
  31. }  

這裏主要註冊WifiP2pReceiver用於接收處理WIFI_P2P_STATE_CHANGED_ACTION、WIFI_P2P_PEERS_CHANGED_ACTION、WIFI_P2P_CONNECTION_CHANGED_ACTION、WIFI_P2P_THIS_DEVICE_CHANGED_ACTION消息,然後註冊ContentObserver來監控Settings.Global這個數據庫裏面的WIFI_DISPLAY_ON、WIFI_DISPLAY_CERTIFICATION_ON和WIFI_DISPLAY_WPS_CONFIG,這裏比較重要,我們後面會看到在WifiDisplaySettings裏面enable wifi display的時候,就會走到這個地方來。接着調用updateSettings來處理默認是否打開Wifi display,這裏默認是關閉的,我們後面再來分析這一塊。


接着來看MediaRouterService和MediaRouter,MediaRouter通過AIDL調用MediaRouterService的實現來完成一些工作。在SystemServer啓動MediaRouterService的時候,主要創建一個MediaRouterService,然後調用它的systemRunning方法,代碼如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public MediaRouterService(Context context) {  
  2.     mContext = context;  
  3.     Watchdog.getInstance().addMonitor(this);  
  4. }  
  5.   
  6. public void systemRunning() {  
  7.     IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);  
  8.     mContext.registerReceiver(new BroadcastReceiver() {  
  9.         @Override  
  10.         public void onReceive(Context context, Intent intent) {  
  11.             if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {  
  12.                 switchUser();  
  13.             }  
  14.         }  
  15.     }, filter);  
  16.   
  17.     switchUser();  
  18. }  

上面的方法比較簡單,主要就是接收ACTION_USER_SWITCHED,這是關於多用戶切換的操作。MediaRouterService的工作比較少,主要都是MediaRouter通過AIDL調用完成,接下來去看MediaRouter的部分,在Android官方文檔中有說明MediaRouter的調用方法:

A MediaRouter is retrieved through Context.getSystemService() of a Context.MEDIA_ROUTER_SERVICE這樣系統是實例化一個MediaRouter對象並返回,下面來看它的構造函數:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public MediaRouter(Context context) {  
  2.     synchronized (Static.class) {  
  3.         if (sStatic == null) {  
  4.             final Context appContext = context.getApplicationContext();  
  5.             sStatic = new Static(appContext);  
  6.             sStatic.startMonitoringRoutes(appContext);  
  7.         }  
  8.     }  
  9. }  
  10.   
  11.     Static(Context appContext) {  
  12.         mAppContext = appContext;  
  13.         mResources = Resources.getSystem();  
  14.         mHandler = new Handler(appContext.getMainLooper());  
  15.   
  16.         IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);  
  17.         mAudioService = IAudioService.Stub.asInterface(b);  
  18.   
  19.         mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);  
  20.   
  21.         mMediaRouterService = IMediaRouterService.Stub.asInterface(  
  22.                 ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));  
  23.   
  24.         mSystemCategory = new RouteCategory(  
  25.                 com.android.internal.R.string.default_audio_route_category_name,  
  26.                 ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false);  
  27.         mSystemCategory.mIsSystem = true;  
  28.   
  29.         mCanConfigureWifiDisplays = appContext.checkPermission(  
  30.                 Manifest.permission.CONFIGURE_WIFI_DISPLAY,  
  31.                 Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED;  
  32.     }  

MediaRouter中主要通過Static對象來實現其大多數的方法,Static就是一個單例模式,先看Static的構造函數,也可以通過上面的圖看到,MediaRouter包含DisplayManager對象和MediaRouterService的BpBinder引用,MediaRouter還持有AudioService的BpBind,用於控制audio數據的輸出設備,例如可以用於藍牙A2DP中使用。接着看Static的startMonitoringRoutes方法:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. void startMonitoringRoutes(Context appContext) {  
  2.     mDefaultAudioVideo = new RouteInfo(mSystemCategory);  
  3.     mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name;  
  4.     mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;  
  5.     mDefaultAudioVideo.updatePresentationDisplay();  
  6.     addRouteStatic(mDefaultAudioVideo);  
  7.   
  8.     // This will select the active wifi display route if there is one.  
  9.     updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus());  
  10.   
  11.     appContext.registerReceiver(new WifiDisplayStatusChangedReceiver(),  
  12.             new IntentFilter(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED));  
  13.     appContext.registerReceiver(new VolumeChangeReceiver(),  
  14.             new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));  
  15.   
  16.     mDisplayService.registerDisplayListener(this, mHandler);  
  17.   
  18.   
  19.     // Bind to the media router service.  
  20.     rebindAsUser(UserHandle.myUserId());  
  21.   
  22.     // Select the default route if the above didn't sync us up  
  23.     // appropriately with relevant system state.  
  24.     if (mSelectedRoute == null) {  
  25.         selectDefaultRouteStatic();  
  26.     }  
  27. }  


首先註冊系統中默認的AudioVideo輸出設備,如果有處於活動狀態的wifi display連接,就記錄下當前處於活動連接的設備,默認爲空。上面會註冊兩個broadcastReceiver,一個用於接收ACTION_WIFI_DISPLAY_STATUS_CHANGED,另一個接收VOLUME_CHANGED_ACTION,我們主要看前面接收ACTION_WIFI_DISPLAY_STATUS_CHANGED的receiver,如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. static class WifiDisplayStatusChangedReceiver extends BroadcastReceiver {  
  2.     @Override  
  3.     public void onReceive(Context context, Intent intent) {  
  4.         if (intent.getAction().equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {  
  5.             updateWifiDisplayStatus((WifiDisplayStatus) intent.getParcelableExtra(  
  6.                     DisplayManager.EXTRA_WIFI_DISPLAY_STATUS));  
  7.         }  
  8.     }  
上面接收ACTION_WIFI_DISPLAY_STATUS_CHANGED,從Intent裏面取出WifiDisplayStatus對象,WifiDisplayStatus內部的變量如下:

mFeatureState 表明現在wifi display是關閉還是打開狀態
mScanState 表現現在wifi display是否在scanning狀態
mActiveDisplayState 表明現在wifi display是在連接還是無連接狀態
mActiveDisplay 處於正在連接或者連接中的WifiDisplay對象
mDisplays 掃描到的WifiDisplay對象數組
mSessionInfo 用於過Miracast認證時用


然後向DisplayManager註冊一個回調函數,當有顯示設備增加、刪除或者改變的時候,就會有相應的回調函數來通知Static對象。接着綁定MediaRouterService:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. void rebindAsUser(int userId) {  
  2.     if (mCurrentUserId != userId || userId < 0 || mClient == null) {  
  3.   
  4.         mCurrentUserId = userId;  
  5.   
  6.         try {  
  7.             Client client = new Client();  
  8.             mMediaRouterService.registerClientAsUser(client,  
  9.                     mAppContext.getPackageName(), userId);  
  10.             mClient = client;  
  11.         } catch (RemoteException ex) {  
  12.             Log.e(TAG, "Unable to register media router client.", ex);  
  13.         }  
  14.   
  15.         publishClientDiscoveryRequest();  
  16.         publishClientSelectedRoute(false);  
  17.         updateClientState();  
  18.     }  
  19. }  


Enable WifiDisplay

當用戶進入WifiDisplaySettings界面,會調用其對應的onCreate和onStart方法:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public void onCreate(Bundle icicle) {  
  2.     super.onCreate(icicle);  
  3.   
  4.     final Context context = getActivity();  
  5.     mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE);  
  6.     mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);  
  7.     mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);  
  8.     mWifiP2pChannel = mWifiP2pManager.initialize(context, Looper.getMainLooper(), null);  
  9.   
  10.     addPreferencesFromResource(R.xml.wifi_display_settings);  
  11.     setHasOptionsMenu(true);  
  12. }  
  13.   
  14. public void onStart() {  
  15.     super.onStart();  
  16.     mStarted = true;  
  17.   
  18.     final Context context = getActivity();  
  19.     IntentFilter filter = new IntentFilter();  
  20.     filter.addAction(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);  
  21.     context.registerReceiver(mReceiver, filter);  
  22.   
  23.     getContentResolver().registerContentObserver(Settings.Global.getUriFor(  
  24.             Settings.Global.WIFI_DISPLAY_ON), false, mSettingsObserver);  
  25.     getContentResolver().registerContentObserver(Settings.Global.getUriFor(  
  26.             Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON), false, mSettingsObserver);  
  27.     getContentResolver().registerContentObserver(Settings.Global.getUriFor(  
  28.             Settings.Global.WIFI_DISPLAY_WPS_CONFIG), false, mSettingsObserver);  
  29.   
  30.     mRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mRouterCallback,  
  31.             MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);  
  32.   
  33.     update(CHANGE_ALL);  
  34. }  

首先註冊對ACTION_WIFI_DISPLAY_STATUS_CHANGED的receiver,這個broadcast會在WifiDisplayAdapter裏面當wifi display的狀態發送改變時發送,包括掃描到新的設備、開始連接、連接成功、斷開等消息都會被這個receiver接收到,後面我們會來分析這個receiver幹了什麼,然後在onStart中想MediaRouter對象註冊一個callback函數,用於獲取系統中remote display的相關回調信息。然後類似WifiDisplayController一樣,註冊一些對數據庫改變的ContentObserver。接着來看MediaRouter.addCallback的實現:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public void addCallback(int types, Callback cb, int flags) {  
  2.     CallbackInfo info;  
  3.     int index = findCallbackInfo(cb);  
  4.     if (index >= 0) {  
  5.         info = sStatic.mCallbacks.get(index);  
  6.         info.type |= types;  
  7.         info.flags |= flags;  
  8.     } else {  
  9.         info = new CallbackInfo(cb, types, flags, this);  
  10.         sStatic.mCallbacks.add(info);  
  11.     }  
  12.     sStatic.updateDiscoveryRequest();  
  13. }  

Static的mCallbacks是一個CopyOnWriteArrayList數組,記錄所有註冊到MediaRouter中的回調函數。如果已經向MediaRouter註冊過這個callback,則更新相關的type和flag;如果沒有註冊,則新建一個CallbackInfo對象並添加到mCallbacks數組中。然後調用Static的updateDiscoveryRequest去更新是否需要發送Discovery request請求:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. void updateDiscoveryRequest() {  
  2.             final int count = mCallbacks.size();  
  3.             for (int i = 0; i < count; i++) {  
  4.                 CallbackInfo cbi = mCallbacks.get(i);  
  5.                 if ((cbi.flags & (CALLBACK_FLAG_PERFORM_ACTIVE_SCAN  
  6.                         | CALLBACK_FLAG_REQUEST_DISCOVERY)) != 0) {  
  7.                     // Discovery explicitly requested.  
  8.                     routeTypes |= cbi.type;  
  9.                 } else if ((cbi.flags & CALLBACK_FLAG_PASSIVE_DISCOVERY) != 0) {  
  10.                     // Discovery only passively requested.  
  11.                     passiveRouteTypes |= cbi.type;  
  12.                 } else {  
  13.                     // Legacy case since applications don't specify the discovery flag.  
  14.                     // Unfortunately we just have to assume they always need discovery  
  15.                     // whenever they have a callback registered.  
  16.                     routeTypes |= cbi.type;  
  17.                 }  
  18.                 if ((cbi.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {  
  19.                     activeScan = true;  
  20.                     if ((cbi.type & ROUTE_TYPE_REMOTE_DISPLAY) != 0) {  
  21.                         activeScanWifiDisplay = true;  
  22.                     }  
  23.                 }  
  24.             }  
  25.             if (routeTypes != 0 || activeScan) {  
  26.                 // If someone else requests discovery then enable the passive listeners.  
  27.                 // This is used by the MediaRouteButton and MediaRouteActionProvider since  
  28.                 // they don't receive lifecycle callbacks from the Activity.  
  29.                 routeTypes |= passiveRouteTypes;  
  30.             }  
  31.   
  32.             // Update wifi display scanning.  
  33.             // TODO: All of this should be managed by the media router service.  
  34.             if (mCanConfigureWifiDisplays) {  
  35.                 if (mSelectedRoute != null  
  36.                         && mSelectedRoute.matchesTypes(ROUTE_TYPE_REMOTE_DISPLAY)) {  
  37.                     // Don't scan while already connected to a remote display since  
  38.                     // it may interfere with the ongoing transmission.  
  39.                     activeScanWifiDisplay = false;  
  40.                 }  
  41.                 if (activeScanWifiDisplay) {  
  42.                     if (!mActivelyScanningWifiDisplays) {  
  43.                         mActivelyScanningWifiDisplays = true;  
  44.                         mDisplayService.startWifiDisplayScan();  
  45.                     }  
  46.                 } else {  
  47.                     if (mActivelyScanningWifiDisplays) {  
  48.                         mActivelyScanningWifiDisplays = false;  
  49.                         mDisplayService.stopWifiDisplayScan();  
  50.                     }  
  51.                 }  
  52.             }  
  53.         }  

這個函數體比較長,主要通過註冊的一系列的callback類型來決定是否要進行wifiDisplay scan的動作,根據在WifiDisplaySettings裏面註冊callback的方法:    mRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mRouterCallback,
                MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN),上面函數中的activeScanWifiDisplay會爲true,接着會調用DisplayManagerService中的startWifiDisplayScan,如下圖。
這裏會通過WifiDisplayAdapter調用到WifiDisplayController的updateScanState動作,我們到updateScanState中去分析:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void updateScanState() {  
  2.     if (mScanRequested && mWfdEnabled && mDesiredDevice == null) {  
  3.         if (!mDiscoverPeersInProgress) {  
  4.             Slog.i(TAG, "Starting Wifi display scan.");  
  5.             mDiscoverPeersInProgress = true;  
  6.             handleScanStarted();  
  7.             tryDiscoverPeers();  
  8.         }  
  9.     } else {  
  10.         if (mDiscoverPeersInProgress) {  
  11.             // Cancel automatic retry right away.  
  12.             mHandler.removeCallbacks(mDiscoverPeers);  
  13.   
  14.             if (mDesiredDevice == null || mDesiredDevice == mConnectedDevice) {  
  15.                 Slog.i(TAG, "Stopping Wifi display scan.");  
  16.                 mDiscoverPeersInProgress = false;  
  17.                 stopPeerDiscovery();  
  18.                 handleScanFinished();  
  19.             }  
  20.         }  
  21.     }  
  22. }  

當初次進入到WifiDisplaySettings中,並沒有去optionMenu中enable wifi display時,上面code中的mWfdEnabled爲false,所以會跳出前面的if語句;後面的else語句中mDiscoverPeersInProgress也爲false,因爲這個變量只有在scan時纔會被置爲true。

接着來分析當用戶點擊了optionMenu中enable wifi display後的流程,先看WifiDisplaySettings的代碼:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public boolean onOptionsItemSelected(MenuItem item) {  
  2.     switch (item.getItemId()) {  
  3.         case MENU_ID_ENABLE_WIFI_DISPLAY:  
  4.             mWifiDisplayOnSetting = !item.isChecked();  
  5.             item.setChecked(mWifiDisplayOnSetting);  
  6.             Settings.Global.putInt(getContentResolver(),  
  7.                     Settings.Global.WIFI_DISPLAY_ON, mWifiDisplayOnSetting ? 1 : 0);  

這裏首先改變OptionMenu的狀態,並置mWifiDisplayOnSetting爲上次MenuItem相反的狀態,然後改變Settings.Global數據庫中WIFI_DISPLAY_ON的指爲1。前面我們介紹過,在WifiDisplaySettings和WifiDisplayController都有註冊ContentObserver來監控這個值的變化。其中WifiDisplaySettings在監控到這個值的變化後,主要是調用MediaRouter和DisplayManager的方法去獲取系統中已經掃描到的remote display設備,並更新到listview列表上,顯然這時候還沒有開始scan,所以listview列表爲空。接着看WifiDisplayController處理ContentOberver的代碼:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void updateSettings() {  
  2.     final ContentResolver resolver = mContext.getContentResolver();  
  3.     mWifiDisplayOnSetting = Settings.Global.getInt(resolver,  
  4.             Settings.Global.WIFI_DISPLAY_ON, 0) != 0;  
  5.     mWifiDisplayCertMode = Settings.Global.getInt(resolver,  
  6.             Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON, 0) != 0;  
  7.   
  8.     mWifiDisplayWpsConfig = WpsInfo.INVALID;  
  9.     if (mWifiDisplayCertMode) {  
  10.         mWifiDisplayWpsConfig = Settings.Global.getInt(resolver,  
  11.               Settings.Global.WIFI_DISPLAY_WPS_CONFIG, WpsInfo.INVALID);  
  12.     }  
  13.   
  14.     updateWfdEnableState();  
  15. }  

這裏主要置mWifiDisplayOnSetting爲true,然後就調用updateWfdEnableState去更新wfd的狀態:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void updateWfdEnableState() {  
  2.     if (mWifiDisplayOnSetting && mWifiP2pEnabled) {  
  3.         // WFD should be enabled.  
  4.         if (!mWfdEnabled && !mWfdEnabling) {  
  5.             mWfdEnabling = true;  
  6.   
  7.             WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();  
  8.             wfdInfo.setWfdEnabled(true);  
  9.             wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);  
  10.             wfdInfo.setSessionAvailable(true);  
  11.             wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);  
  12.             wfdInfo.setMaxThroughput(MAX_THROUGHPUT);  
  13.             mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {  
  14.                 @Override  
  15.                 public void onSuccess() {  
  16.                     if (DEBUG) {  
  17.                         Slog.d(TAG, "Successfully set WFD info.");  
  18.                     }  
  19.                     if (mWfdEnabling) {  
  20.                         mWfdEnabling = false;  
  21.                         mWfdEnabled = true;  
  22.                         reportFeatureState();  
  23.                         updateScanState();  
  24.                     }  
  25.                 }  
  26.   
  27.                 @Override  
  28.                 public void onFailure(int reason) {  
  29.                     if (DEBUG) {  
  30.                         Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");  
  31.                     }  
  32.                     mWfdEnabling = false;  
  33.                 }  
  34.             });  
  35.         }  

首先調用WifiP2pMananger的setWFDInfo把與wifi display相關的信息設置到wpa_supplicant,這些信息包括enable狀態、device type(指爲source還是sink)、session available(當前可否連接)、control port(用於rtsp連接)、maxThroughput(吞吐量),這些信息最終會隨着P2P的IE信息在掃描階段被對方知道。接着會調用reportFeatureState來通知WifiDisplayAdapter相應狀態的變化,這裏我們先看一下下面的流程圖來了解一下WifiDisplaySettings、MediaRouter、DisplayMananger、WifiDisplayAdapter、WifiDisplayController是如何相互通知信息的,這其中有簡單的callback,也有發送/接收broadcast,如下圖:


通過上面的圖我們可以看到實線部分是調用關係,虛線部分是回調關係。接着我們來看reportFeatureState的實現:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void reportFeatureState() {  
  2.     final int featureState = computeFeatureState();  
  3.     mHandler.post(new Runnable() {  
  4.         @Override  
  5.         public void run() {  
  6.             mListener.onFeatureStateChanged(featureState);  
  7.         }  
  8.     });  
  9. }  
  10.   
  11. private int computeFeatureState() {  
  12.     if (!mWifiP2pEnabled) {  
  13.         return WifiDisplayStatus.FEATURE_STATE_DISABLED;  
  14.     }  
  15.     return mWifiDisplayOnSetting ? WifiDisplayStatus.FEATURE_STATE_ON :  
  16.             WifiDisplayStatus.FEATURE_STATE_OFF;  
  17. }  

直接回調WifiDisplayListener的onFeatureStateChanged,從上面的圖我們可以看着WifiDisplayListener會由WifiDisplayAdapter註冊的,去看這部分的實現:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1.     public void onFeatureStateChanged(int featureState) {  
  2.         synchronized (getSyncRoot()) {  
  3.             if (mFeatureState != featureState) {  
  4.                 mFeatureState = featureState;  
  5.                 scheduleStatusChangedBroadcastLocked();  
  6.             }  
  7.         }  
  8.     }  
  9.   
  10. private void scheduleStatusChangedBroadcastLocked() {  
  11.     mCurrentStatus = null;  
  12.     if (!mPendingStatusChangeBroadcast) {  
  13.         mPendingStatusChangeBroadcast = true;  
  14.         mHandler.sendEmptyMessage(MSG_SEND_STATUS_CHANGE_BROADCAST);  
  15.     }  
  16. }  

這裏最後通過WifiDisplayHandler的sendEmptyMessage的方法實現,目的是不要卡住了WifiDisplayController後面代碼的執行,來看WifiDisplayHandler如何處理MSG_SEND_STATUS_CHANGE_BROADCAST:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public void handleMessage(Message msg) {  
  2.     switch (msg.what) {  
  3.         case MSG_SEND_STATUS_CHANGE_BROADCAST:  
  4.             handleSendStatusChangeBroadcast();  
  5.             break;  
  6.   
  7.         case MSG_UPDATE_NOTIFICATION:  
  8.             handleUpdateNotification();  
  9.             break;  
  10.     }  
  11.   
  12. ate void handleSendStatusChangeBroadcast() {  
  13. final Intent intent;  
  14. synchronized (getSyncRoot()) {  
  15.     if (!mPendingStatusChangeBroadcast) {  
  16.         return;  
  17.     }  
  18.   
  19.     mPendingStatusChangeBroadcast = false;  
  20.     intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);  
  21.     intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);  
  22.     intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,  
  23.             getWifiDisplayStatusLocked());  
  24. }  
  25.   
  26. // Send protected broadcast about wifi display status to registered receivers.  
  27. getContext().sendBroadcastAsUser(intent, UserHandle.ALL);  

上面的代碼都比較簡單,在getWifiDisplayStatusLocked中會根據WifiDisplayAdapter中的變量mFeatureState、mScanState、mActiveDisplayState、mActiveDisplay、mDisplays、mSessionInfo去構造一個WifiDisplayStatus對象,在前面我們介紹過這幾個變量的含義了,當然這幾個變量會從WifiDisplayListener的各個callback分別去改變自己的值。接着我們到MediaRouter中去看如何處理這個broadcastReceiver,前面我們已經講過了,WifiDisplayStatusChangedReceiver會接收這個broadcast,然後調用updateWifiDisplayStatus來更新狀態,我們稍後來看這部分的實現。回到WifiDisplayController的updateWfdEnableState方法中,接着會調用updateScanState方法開始掃描WifiDisplay設備:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void updateScanState() {  
  2.     if (mScanRequested && mWfdEnabled && mDesiredDevice == null) {  
  3.         if (!mDiscoverPeersInProgress) {  
  4.             Slog.i(TAG, "Starting Wifi display scan.");  
  5.             mDiscoverPeersInProgress = true;  
  6.             handleScanStarted();  
  7.             tryDiscoverPeers();  
  8.         }  
  9.     }  

handleScanStarted用於通知WifiDisplayAdapter掃描開始了,當然WifiDisplayAdapter也會發broadcast給MediaRouter。接着會調用tryDiscoverPeers:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void tryDiscoverPeers() {  
  2.     mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {  
  3.         @Override  
  4.         public void onSuccess() {  
  5.             if (DEBUG) {  
  6.                 Slog.d(TAG, "Discover peers succeeded.  Requesting peers now.");  
  7.             }  
  8.             if (mDiscoverPeersInProgress) {  
  9.                 requestPeers();  
  10.             }  
  11.         }  
  12.     mHandler.postDelayed(mDiscoverPeers, DISCOVER_PEERS_INTERVAL_MILLIS);  
  13. }  

這裏調用WifiP2pManager的discoverPeers去掃描所有的p2p設備,比較重要是後面有發一個delay message,表示每間隔10秒就去發一下P2P_FIND。當然下了P2P_FIND命令後,並不能馬上獲取到對方設備,但因爲我們前面有講過在/data/system/display-manager-state.xml有保存過前面連接過的設備列表,所以這裏會馬上調用requestPeers去獲取設備列表。當然在WifiDisplayController也會註冊對WIFI_P2P_PEERS_CHANGED_ACTION的receiver,最終還是會調用reqeustPeers去獲取所有掃描到的設備列表,下面來看這個函數的實現:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  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.   
  15.                 if (isWifiDisplay(device)) {  
  16.                     mAvailableWifiDisplayPeers.add(device);  
  17.                 }  
  18.             }  
  19.   
  20.             if (mDiscoverPeersInProgress) {  
  21.                 handleScanResults();  
  22.             }  
  23.         }  
  24.     });  
  25. }  

首先從掃描的設備列表中過濾掉不能做wifi display的設備,主要從三個方面過濾,一是純粹的P2P設備,不會待用WfdInfo;第二是帶有WfdInfo,但是暫時沒有被enable;三是隻能是PrimarySinkDevice,看起來Android還不支持SecondSink。並將過濾掉剩下的設備加入到mAvailableWifiDisplayPeers列表中,接着調用handleScanResults來組裝WifiDisplay列表數組並notify給WifiDisplayAdapter:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void handleScanResults() {  
  2.     final int count = mAvailableWifiDisplayPeers.size();  
  3.     final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count);  
  4.     for (int i = 0; i < count; i++) {  
  5.         WifiP2pDevice device = mAvailableWifiDisplayPeers.get(i);  
  6.         displays[i] = createWifiDisplay(device);  
  7.         updateDesiredDevice(device);  
  8.     }  
  9.   
  10.     mHandler.post(new Runnable() {  
  11.         @Override  
  12.         public void run() {  
  13.             mListener.onScanResults(displays);  
  14.         }  
  15.     });  
  16. }  

這裏首先根據mAvailableWifiDisplayPeers的數目創建一個WifiDisplay數組,然後一個個構造WifiDisplay對象,WifiDiplay對象包含以下幾個變量:
mDeviceAddress 設備的Mac地址
mDeviceName 設備的名字
mDeviceAlias 設備的別名,一般爲NULL
mIsAvailable 是否可用狀態
mCanConnect WfdInfo中的SessionAvailable是否爲1
mIsRemembered 是否被記錄的

接着調用updateDesiredDevice用於判斷掃描到的這個設備是否是現在正在連接或者連接上的設備,如果是,則更新它的一些信息,以後在連接Wifi display的時候再來分析這一塊。接着就會向WifiDisplayAdapter回調onScanResults,回調函數中帶有已經掃描到的wifi display設備列表(如果有):
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public void onScanResults(WifiDisplay[] availableDisplays) {  
  2.     synchronized (getSyncRoot()) {  
  3.         availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(  
  4.                 availableDisplays);  
  5.   
  6.         boolean changed = !Arrays.equals(mAvailableDisplays, availableDisplays);  
  7.   
  8.         // Check whether any of the available displays changed canConnect status.  
  9.         for (int i = 0; !changed && i<availableDisplays.length; i++) {  
  10.             changed = availableDisplays[i].canConnect()  
  11.                     != mAvailableDisplays[i].canConnect();  
  12.         }  
  13.   
  14.         if (changed) {  
  15.             mAvailableDisplays = availableDisplays;  
  16.             fixRememberedDisplayNamesFromAvailableDisplaysLocked();  
  17.             updateDisplaysLocked();  
  18.             scheduleStatusChangedBroadcastLocked();  
  19.         }  
  20.     }  
  21. }  

這裏首先調用PersistentDateStore的applyWifiDisplayAliases方法去判斷掃描到的設備中有沒有以前連接過並記錄下來的wifi display設備,比較方法是比較兩者的MAC地址,如果在PersistentDateStore中找到,再比較兩者的別名(Alias),如果不相同則更新results列表,細節的代碼可以看applyWifiDisplayAlias中的實現。
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) {  
  2.     WifiDisplay[] results = displays;  
  3.     if (results != null) {  
  4.         int count = displays.length;  
  5.         for (int i = 0; i < count; i++) {  
  6.             WifiDisplay result = applyWifiDisplayAlias(displays[i]);  
  7.             if (result != displays[i]) {  
  8.                 if (results == displays) {  
  9.                     results = new WifiDisplay[count];  
  10.                     System.arraycopy(displays, 0, results, 0, count);  
  11.                 }  
  12.                 results[i] = result;  
  13.             }  
  14.         }  
  15.     }  
  16.     return results;  
  17. }  

回到上面的onScanResults中,接着判斷剛掃描到的設備列表(availableDisplays)和之前存儲的設備列表(mAvailableDisplays)之間有沒有變化,可以數組內容以及是否可連兩個方面檢查。如果有變化,則把剛掃描到的設備列表(availableDisplays)賦值給存儲的設備列表(mAvailableDisplays)。接下來調用fixRememberedDisplayNamesFromAvailableDisplaysLocked來更新PersistentDateStore中存儲的已經連接過的wifi display設備,更新的條件是設備的MAC地址一樣,但設備的DeviceName和DeviceAlias有變化,這是就要更新到PersistentDateStore中,代碼如下:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void fixRememberedDisplayNamesFromAvailableDisplaysLocked() {  
  2.     boolean changed = false;  
  3.     for (int i = 0; i < mRememberedDisplays.length; i++) {  
  4.         WifiDisplay rememberedDisplay = mRememberedDisplays[i];  
  5.         WifiDisplay availableDisplay = findAvailableDisplayLocked(  
  6.                 rememberedDisplay.getDeviceAddress());  
  7.         if (availableDisplay != null && !rememberedDisplay.equals(availableDisplay)) {  
  8.             mRememberedDisplays[i] = availableDisplay;  
  9.             changed |= mPersistentDataStore.rememberWifiDisplay(availableDisplay);  
  10.         }  
  11.     }  
  12.     if (changed) {  
  13.         mPersistentDataStore.saveIfNeeded();  
  14.     }  
  15. }  
如果掃描到的設備列表中有wifi display設備的名字或者別名發生了變化,就會調用到PersistentDataStore.saveIfNeeded方法把數據寫到/data/system/display-manager-state.xml中。

回到onScanResults中,接下來會調用updateDisplaysLocked來更新返回給MediaRouter的設備列表信息,在這裏會把掃描到的設備以及之前存儲下來的設備做一次合併,共同保存到mDisplays數組中,後面在發送broadcast的時候,就會把mDisplays保存到WifiDisplayStatus對象中,並在broadcast帶上這個對象。
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void updateDisplaysLocked() {  
  2.     List<WifiDisplay> displays = new ArrayList<WifiDisplay>(  
  3.             mAvailableDisplays.length + mRememberedDisplays.length);  
  4.     boolean[] remembered = new boolean[mAvailableDisplays.length];  
  5.     for (WifiDisplay d : mRememberedDisplays) {  
  6.         boolean available = false;  
  7.         for (int i = 0; i < mAvailableDisplays.length; i++) {  
  8.             if (d.equals(mAvailableDisplays[i])) {  
  9.                 remembered[i] = available = true;  
  10.                 break;  
  11.             }  
  12.         }  
  13.         if (!available) {  
  14.             displays.add(new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(),  
  15.                     d.getDeviceAlias(), falsefalsetrue));  
  16.         }  
  17.     }  
  18.     for (int i = 0; i < mAvailableDisplays.length; i++) {  
  19.         WifiDisplay d = mAvailableDisplays[i];  
  20.         displays.add(new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(),  
  21.                 d.getDeviceAlias(), true, d.canConnect(), remembered[i]));  
  22.     }  
  23.     mDisplays = displays.toArray(WifiDisplay.EMPTY_ARRAY);  
  24. }  

上面的實現中先從mRememberedDisplays逐個添加wifi display設備到displays數組中,如果在mAvailableDisplays有相同的設備,則不添加到displays數組;後面再把mAvailableDisplays所有元素添加到displays數組,並全部賦值給mDisplays數組。

再回到onScanResults中,就會調用scheduleStatusChangedBroadcastLocked向WifiDisplayHandler發送MSG_SEND_STATUS_CHANGE_BROADCAST消息,這個我們在前面已經講過了,然後會發送broadcast,並帶上一個WifiDisplayStatus對象。現在我們再到MediaRouter和WifiDisplaySettings中看如何處理這個broadcast,先來看MediaRouter如何解析WifiDisplayStatus對象。updateWifiDisplayStatus的實現如下:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. static void updateWifiDisplayStatus(WifiDisplayStatus status) {  
  2.     WifiDisplay[] displays;  
  3.     WifiDisplay activeDisplay;  
  4.     if (status.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {  
  5.         displays = status.getDisplays();  
  6.         activeDisplay = status.getActiveDisplay();  
  7.     } else {  
  8.         displays = WifiDisplay.EMPTY_ARRAY;  
  9.         activeDisplay = null;  
  10.     }  
  11.     String activeDisplayAddress = activeDisplay != null ?  
  12.             activeDisplay.getDeviceAddress() : null;  
  13.   
  14.     // Add or update routes.  
  15.     for (int i = 0; i < displays.length; i++) {  
  16.         final WifiDisplay d = displays[i];  
  17.         if (shouldShowWifiDisplay(d, activeDisplay)) {  
  18.             RouteInfo route = findWifiDisplayRoute(d);  
  19.             if (route == null) {  
  20.                 route = makeWifiDisplayRoute(d, status);  
  21.                 addRouteStatic(route);  
  22.             } else {  
  23.                 String address = d.getDeviceAddress();  
  24.                 boolean disconnected = !address.equals(activeDisplayAddress)  
  25.                         && address.equals(sStatic.mPreviousActiveWifiDisplayAddress);  
  26.                 updateWifiDisplayRoute(route, d, status, disconnected);  
  27.             }  
  28.             if (d.equals(activeDisplay)) {  
  29.                 selectRouteStatic(route.getSupportedTypes(), route, false);  
  30.             }  
  31.         }  
  32.     }  
  33.   
  34.     // Remove stale routes.  
  35.     for (int i = sStatic.mRoutes.size(); i-- > 0; ) {  
  36.         RouteInfo route = sStatic.mRoutes.get(i);  
  37.         if (route.mDeviceAddress != null) {  
  38.             WifiDisplay d = findWifiDisplay(displays, route.mDeviceAddress);  
  39.             if (d == null || !shouldShowWifiDisplay(d, activeDisplay)) {  
  40.                 removeRouteStatic(route);  
  41.             }  
  42.         }  
  43.     }  
  44.   
  45.     sStatic.mPreviousActiveWifiDisplayAddress = activeDisplayAddress;  
  46. }  

上面的代碼中,首先從WifiDisplayStatus取出已經掃描到的WifiDisplay設備數組和當前處於連接狀態的WifiDisplay設備,然後shouldShowWifiDisplay用於過濾是否將這個wifi display設備加入到mRoutes數組中,判斷條件是這個設備已經連過並且有保存在PersistentDateStore或者這個設備就是當前正在連接中的設備,對於其它的設備並沒有加入到mRoutes中,這裏就有個疑問了,其它沒連過的設備將在哪裏加入呢? 我們後面分析WifiDisplaySettings再來看這部分。如果在mRoutes沒有找到相同的wifi display設備,就會把這個設備加入到mRoutes中,並通知WifiDisplaySettings相應的變化;如果在mRoutes存在相同的wifi display設備,則檢查它的名字或者狀態(available、canConnect)有沒有變化,如果有變化,則通知WifiDisplaySettings相應的改變。selectRouteStatic用於更新是否默認的router並dispatch相應的回調消息。最後會從mRoutes踢出有錯誤的wifi display設備。

我的一些簡單理解:MediaRouter只保存已經配對上的remote display設備,包括Wifi diplay、藍牙A2DP設備、chromecast設備等,用於提供給其它應用程序使用,比如youtube可以直接chromecast,當我們前面有成功和一個chromecast設備配對過後,youtube應用就可以從MediaRouter對象中獲取到當前已經配對的chromecast設備信息,並可以把youtube的視頻推送到chromecast上面播放;再舉個例子,百度視頻應用可以訪問MediaRouter中的wifi display設備,當我們設備中有已經連接或已經保存的wifi display設備時,就可以很方便的從直接百度視頻上面直接開始wifi display,而不需要用戶再去Settings裏面掃描連接。

再來看WifiDisplaySettings中如何處理MSG_SEND_STATUS_CHANGE_BROADCAST:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private final BroadcastReceiver mReceiver = new BroadcastReceiver() {  
  2.     @Override  
  3.     public void onReceive(Context context, Intent intent) {  
  4.         String action = intent.getAction();  
  5.         if (action.equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {  
  6.             scheduleUpdate(CHANGE_WIFI_DISPLAY_STATUS);  
  7.         }  
  8.     }  
  9. };  

從MediaRouter中的callback消息也會進入到scheduleUpdate中,只是後面的參數不一樣,通過callback進來的參數是CHANGE_ROUTES,而broadcast進來的參數是CHANGE_WIFI_DISPLAY_STATUS,來看scheduleUpdate,最終實現是mUpdateRunnable中:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void update(int changes) {  
  2.     boolean invalidateOptions = false;  
  3.   
  4.     // Update wifi display state.  
  5.     if ((changes & CHANGE_WIFI_DISPLAY_STATUS) != 0) {  
  6.         mWifiDisplayStatus = mDisplayManager.getWifiDisplayStatus();  
  7.   
  8.         // The wifi display feature state may have changed.  
  9.         invalidateOptions = true;  
  10.     }  
  11.   
  12.     // Rebuild the routes.  
  13.     final PreferenceScreen preferenceScreen = getPreferenceScreen();  
  14.     preferenceScreen.removeAll();  
  15.   
  16.     // Add all known remote display routes.  
  17.     final int routeCount = mRouter.getRouteCount();  
  18.     for (int i = 0; i < routeCount; i++) {  
  19.         MediaRouter.RouteInfo route = mRouter.getRouteAt(i);  
  20.         if (route.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) {  
  21.             preferenceScreen.addPreference(createRoutePreference(route));  
  22.         }  
  23.     }  
  24.   
  25.     // Additional features for wifi display routes.  
  26.     if (mWifiDisplayStatus != null  
  27.             && mWifiDisplayStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {  
  28.         // Add all unpaired wifi displays.  
  29.         for (WifiDisplay display : mWifiDisplayStatus.getDisplays()) {  
  30.             if (!display.isRemembered() && display.isAvailable()  
  31.                     && !display.equals(mWifiDisplayStatus.getActiveDisplay())) {  
  32.                 preferenceScreen.addPreference(new UnpairedWifiDisplayPreference(  
  33.                         getActivity(), display));  
  34.             }  
  35.         }  
  36.     }  
  37.   
  38. }  

上面的代碼比較簡單,一個是從MediaRouter中獲取mRoutes數組中存着的remote display設備;一個是從broadcast中的WifiDisplayStatus對象中獲取mDisplay數組,兩者相互合併構建整個listview展現給用戶。至此,wifi display的掃描流程就介紹完了,下面是整體的流程圖:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章