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

網址:http://www.2cto.com/kf/201404/290996.html

?

最近在學習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方法:

?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public DisplayManagerService(Context context, Handler mainHandler) {
????mContext = context;
????mHeadless = SystemProperties.get(SYSTEM_HEADLESS).equals("1");
????mHandler = new DisplayManagerHandler(mainHandler.getLooper());
????mUiHandler = UiThread.getHandler();
????mDisplayAdapterListener = new DisplayAdapterListener();
????mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
????mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER);
}
public void systemReady(boolean safeMode, boolean onlyCore) {
????synchronized (mSyncRoot) {
????????mSafeMode = safeMode;
????????mOnlyCore = onlyCore;
????}
????mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);
}


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

?

?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private final class DisplayManagerHandler extends Handler {
????public DisplayManagerHandler(Looper looper) {
????????super(looper, null, true /*async*/);
????}
????@Override
????public void handleMessage(Message msg) {
????????switch (msg.what) {
????????????case MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER:
????????????????registerDefaultDisplayAdapter();
????????????????break;
????????????case MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS:
????????????????registerAdditionalDisplayAdapters();
????????????????break;


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

?

?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void registerDefaultDisplayAdapter() {
????// Register default display adapter.
????synchronized (mSyncRoot) {
????????if (mHeadless) {
????????????registerDisplayAdapterLocked(new HeadlessDisplayAdapter(
????????????????????mSyncRoot, mContext, mHandler, mDisplayAdapterListener));
????????} else {
????????????registerDisplayAdapterLocked(new LocalDisplayAdapter(
????????????????????mSyncRoot, mContext, mHandler, mDisplayAdapterListener));
????????}
????}
}
private void registerDisplayAdapterLocked(DisplayAdapter adapter) {
????mDisplayAdapters.add(adapter);
????adapter.registerLocked();
}


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

?

?

?
1
2
3
4
5
6
7
8
9
private void registerAdditionalDisplayAdapters() {
????synchronized (mSyncRoot) {
????????if (shouldRegisterNonEssentialDisplayAdaptersLocked()) {
????????????registerOverlayDisplayAdapterLocked();
????????????registerWifiDisplayAdapterLocked();
????????????registerVirtualDisplayAdapterLocked();
????????}
????}
}


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

?

?

?
1
2
3
4
5
6
7
8
9
10
private void registerWifiDisplayAdapterLocked() {
????if (mContext.getResources().getBoolean(
????????????com.android.internal.R.bool.config_enableWifiDisplay)
????????????|| SystemProperties.getInt(FORCE_WIFI_DISPLAY_ENABLE, -1) == 1) {
????????mWifiDisplayAdapter = new WifiDisplayAdapter(
????????????????mSyncRoot, mContext, mHandler, mDisplayAdapterListener,
????????????????mPersistentDataStore);
????????registerDisplayAdapterLocked(mWifiDisplayAdapter);
????}
}


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

?

?

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


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連接/斷開等。

?

?

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

?

這里主要注冊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方法,代碼如下:

?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public MediaRouterService(Context context) {
????mContext = context;
????Watchdog.getInstance().addMonitor(this);
}
public void systemRunning() {
????IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
????mContext.registerReceiver(new BroadcastReceiver() {
????????@Override
????????public void onReceive(Context context, Intent intent) {
????????????if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {
????????????????switchUser();
????????????}
????????}
????}, filter);
????switchUser();
}


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

?

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

?

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


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

?

?

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



?

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

?

?
1
2
3
4
5
6
7
8
static class WifiDisplayStatusChangedReceiver extends BroadcastReceiver {
????@Override
????public void onReceive(Context context, Intent intent) {
????????if (intent.getAction().equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {
????????????updateWifiDisplayStatus((WifiDisplayStatus) intent.getParcelableExtra(
????????????????????DisplayManager.EXTRA_WIFI_DISPLAY_STATUS));
????????}
????}

上面接收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:

?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void rebindAsUser(int userId) {
????if (mCurrentUserId != userId || userId < 0 || mClient == null) {
????????mCurrentUserId = userId;
????????try {
????????????Client client = new Client();
????????????mMediaRouterService.registerClientAsUser(client,
????????????????????mAppContext.getPackageName(), userId);
????????????mClient = client;
????????} catch (RemoteException ex) {
????????????Log.e(TAG, "Unable to register media router client.", ex);
????????}
????????publishClientDiscoveryRequest();
????????publishClientSelectedRoute(false);
????????updateClientState();
????}
}

?

?

Enable WifiDisplay

當用戶進入WifiDisplaySettings界面,會調用其對應的onCreate和onStart方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void onCreate(Bundle icicle) {
????super.onCreate(icicle);
????final Context context = getActivity();
????mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
????mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
????mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
????mWifiP2pChannel = mWifiP2pManager.initialize(context, Looper.getMainLooper(), null);
????addPreferencesFromResource(R.xml.wifi_display_settings);
????setHasOptionsMenu(true);
}
public void onStart() {
????super.onStart();
????mStarted = true;
????final Context context = getActivity();
????IntentFilter filter = new IntentFilter();
????filter.addAction(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
????context.registerReceiver(mReceiver, filter);
????getContentResolver().registerContentObserver(Settings.Global.getUriFor(
????????????Settings.Global.WIFI_DISPLAY_ON), false, mSettingsObserver);
????getContentResolver().registerContentObserver(Settings.Global.getUriFor(
????????????Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON), false, mSettingsObserver);
????getContentResolver().registerContentObserver(Settings.Global.getUriFor(
????????????Settings.Global.WIFI_DISPLAY_WPS_CONFIG), false, mSettingsObserver);
????mRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mRouterCallback,
????????????MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
????update(CHANGE_ALL);
}


首先注冊對ACTION_WIFI_DISPLAY_STATUS_CHANGED的receiver,這個broadcast會在WifiDisplayAdapter里面當wifi display的狀態發送改變時發送,包括掃描到新的設備、開始連接、連接成功、斷開等消息都會被這個receiver接收到,后面我們會來分析這個receiver干了什么,然后在onStart中想MediaRouter對象注冊一個callback函數,用于獲取系統中remote display的相關回調信息。然后類似WifiDisplayController一樣,注冊一些對數據庫改變的ContentObserver。接著來看MediaRouter.addCallback的實現:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public void addCallback(int types, Callback cb, int flags) {
????CallbackInfo info;
????int index = findCallbackInfo(cb);
????if (index >= 0) {
????????info = sStatic.mCallbacks.get(index);
????????info.type |= types;
????????info.flags |= flags;
????} else {
????????info = new CallbackInfo(cb, types, flags, this);
????????sStatic.mCallbacks.add(info);
????}
????sStatic.updateDiscoveryRequest();
}


Static的mCallbacks是一個CopyOnWriteArrayList數組,記錄所有注冊到MediaRouter中的回調函數。如果已經向MediaRouter注冊過這個callback,則更新相關的type和flag;如果沒有注冊,則新建一個CallbackInfo對象并添加到mCallbacks數組中。然后調用Static的updateDiscoveryRequest去更新是否需要發送Discovery request請求:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void updateDiscoveryRequest() {
????????????final int count = mCallbacks.size();
????????????for (int i = 0; i < count; i++) {
????????????????CallbackInfo cbi = mCallbacks.get(i);
????????????????if ((cbi.flags & (CALLBACK_FLAG_PERFORM_ACTIVE_SCAN
????????????????????????| CALLBACK_FLAG_REQUEST_DISCOVERY)) != 0) {
????????????????????// Discovery explicitly requested.
????????????????????routeTypes |= cbi.type;
????????????????} else if ((cbi.flags & CALLBACK_FLAG_PASSIVE_DISCOVERY) != 0) {
????????????????????// Discovery only passively requested.
????????????????????passiveRouteTypes |= cbi.type;
????????????????} else {
????????????????????// Legacy case since applications don't specify the discovery flag.
????????????????????// Unfortunately we just have to assume they always need discovery
????????????????????// whenever they have a callback registered.
????????????????????routeTypes |= cbi.type;
????????????????}
????????????????if ((cbi.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
????????????????????activeScan = true;
????????????????????if ((cbi.type & ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
????????????????????????activeScanWifiDisplay = true;
????????????????????}
????????????????}
????????????}
????????????if (routeTypes != 0 || activeScan) {
????????????????// If someone else requests discovery then enable the passive listeners.
????????????????// This is used by the MediaRouteButton and MediaRouteActionProvider since
????????????????// they don't receive lifecycle callbacks from the Activity.
????????????????routeTypes |= passiveRouteTypes;
????????????}
????????????// Update wifi display scanning.
????????????// TODO: All of this should be managed by the media router service.
????????????if (mCanConfigureWifiDisplays) {
????????????????if (mSelectedRoute != null
????????????????????????&& mSelectedRoute.matchesTypes(ROUTE_TYPE_REMOTE_DISPLAY)) {
????????????????????// Don't scan while already connected to a remote display since
????????????????????// it may interfere with the ongoing transmission.
????????????????????activeScanWifiDisplay = false;
????????????????}
????????????????if (activeScanWifiDisplay) {
????????????????????if (!mActivelyScanningWifiDisplays) {
????????????????????????mActivelyScanningWifiDisplays = true;
????????????????????????mDisplayService.startWifiDisplayScan();
????????????????????}
????????????????} else {
????????????????????if (mActivelyScanningWifiDisplays) {
????????????????????????mActivelyScanningWifiDisplays = false;
????????????????????????mDisplayService.stopWifiDisplayScan();
????????????????????}
????????????????}
????????????}
????????}


這個函數體比較長,主要通過注冊的一系列的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中去分析:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void updateScanState() {
????if (mScanRequested && mWfdEnabled && mDesiredDevice == null) {
????????if (!mDiscoverPeersInProgress) {
????????????Slog.i(TAG, "Starting Wifi display scan.");
????????????mDiscoverPeersInProgress = true;
????????????handleScanStarted();
????????????tryDiscoverPeers();
????????}
????} else {
????????if (mDiscoverPeersInProgress) {
????????????// Cancel automatic retry right away.
????????????mHandler.removeCallbacks(mDiscoverPeers);
????????????if (mDesiredDevice == null || mDesiredDevice == mConnectedDevice) {
????????????????Slog.i(TAG, "Stopping Wifi display scan.");
????????????????mDiscoverPeersInProgress = false;
????????????????stopPeerDiscovery();
????????????????handleScanFinished();
????????????}
????????}
????}
}


當初次進入到WifiDisplaySettings中,并沒有去optionMenu中enable wifi display時,上面code中的mWfdEnabled為false,所以會跳出前面的if語句;后面的else語句中mDiscoverPeersInProgress也為false,因為這個變量只有在scan時才會被置為true。?
接著來分析當用戶點擊了optionMenu中enable wifi display后的流程,先看WifiDisplaySettings的代碼:

?
1
2
3
4
5
6
7
public boolean onOptionsItemSelected(MenuItem item) {
????switch (item.getItemId()) {
????????case MENU_ID_ENABLE_WIFI_DISPLAY:
????????????mWifiDisplayOnSetting = !item.isChecked();
????????????item.setChecked(mWifiDisplayOnSetting);
????????????Settings.Global.putInt(getContentResolver(),
????????????????????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的代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void updateSettings() {
????final ContentResolver resolver = mContext.getContentResolver();
????mWifiDisplayOnSetting = Settings.Global.getInt(resolver,
????????????Settings.Global.WIFI_DISPLAY_ON, 0) != 0;
????mWifiDisplayCertMode = Settings.Global.getInt(resolver,
????????????Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON, 0) != 0;
????mWifiDisplayWpsConfig = WpsInfo.INVALID;
????if (mWifiDisplayCertMode) {
????????mWifiDisplayWpsConfig = Settings.Global.getInt(resolver,
??????????????Settings.Global.WIFI_DISPLAY_WPS_CONFIG, WpsInfo.INVALID);
????}
????updateWfdEnableState();
}


這里主要置mWifiDisplayOnSetting為true,然后就調用updateWfdEnableState去更新wfd的狀態:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private void updateWfdEnableState() {
????if (mWifiDisplayOnSetting && mWifiP2pEnabled) {
????????// WFD should be enabled.
????????if (!mWfdEnabled && !mWfdEnabling) {
????????????mWfdEnabling = true;
????????????WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
????????????wfdInfo.setWfdEnabled(true);
????????????wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
????????????wfdInfo.setSessionAvailable(true);
????????????wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
????????????wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
????????????mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
????????????????@Override
????????????????public void onSuccess() {
????????????????????if (DEBUG) {
????????????????????????Slog.d(TAG, "Successfully set WFD info.");
????????????????????}
????????????????????if (mWfdEnabling) {
????????????????????????mWfdEnabling = false;
????????????????????????mWfdEnabled = true;
????????????????????????reportFeatureState();
????????????????????????updateScanState();
????????????????????}
????????????????}
????????????????@Override
????????????????public void onFailure(int reason) {
????????????????????if (DEBUG) {
????????????????????????Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
????????????????????}
????????????????????mWfdEnabling = false;
????????????????}
????????????});
????????}


首先調用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的實現:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void reportFeatureState() {
????final int featureState = computeFeatureState();
????mHandler.post(new Runnable() {
????????@Override
????????public void run() {
????????????mListener.onFeatureStateChanged(featureState);
????????}
????});
}
private int computeFeatureState() {
????if (!mWifiP2pEnabled) {
????????return WifiDisplayStatus.FEATURE_STATE_DISABLED;
????}
????return mWifiDisplayOnSetting ? WifiDisplayStatus.FEATURE_STATE_ON :
????????????WifiDisplayStatus.FEATURE_STATE_OFF;
}


直接回調WifiDisplayListener的onFeatureStateChanged,從上面的圖我們可以看著WifiDisplayListener會由WifiDisplayAdapter注冊的,去看這部分的實現:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
????public void onFeatureStateChanged(int featureState) {
????????synchronized (getSyncRoot()) {
????????????if (mFeatureState != featureState) {
????????????????mFeatureState = featureState;
????????????????scheduleStatusChangedBroadcastLocked();
????????????}
????????}
????}
private void scheduleStatusChangedBroadcastLocked() {
????mCurrentStatus = null;
????if (!mPendingStatusChangeBroadcast) {
????????mPendingStatusChangeBroadcast = true;
????????mHandler.sendEmptyMessage(MSG_SEND_STATUS_CHANGE_BROADCAST);
????}
}


這里最后通過WifiDisplayHandler的sendEmptyMessage的方法實現,目的是不要卡住了WifiDisplayController后面代碼的執行,來看WifiDisplayHandler如何處理MSG_SEND_STATUS_CHANGE_BROADCAST:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
????public void handleMessage(Message msg) {
????????switch (msg.what) {
????????????case MSG_SEND_STATUS_CHANGE_BROADCAST:
????????????????handleSendStatusChangeBroadcast();
????????????????break;
????????????case MSG_UPDATE_NOTIFICATION:
????????????????handleUpdateNotification();
????????????????break;
????????}
private void handleSendStatusChangeBroadcast() {
????final Intent intent;
????synchronized (getSyncRoot()) {
????????if (!mPendingStatusChangeBroadcast) {
????????????return;
????????}
????????mPendingStatusChangeBroadcast = false;
????????intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
????????intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
????????intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,
????????????????getWifiDisplayStatusLocked());
????}
????// Send protected broadcast about wifi display status to registered receivers.
????getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
}


上面的代碼都比較簡單,在getWifiDisplayStatusLocked中會根據WifiDisplayAdapter中的變量mFeatureState、mScanState、mActiveDisplayState、mActiveDisplay、mDisplays、mSessionInfo去構造一個WifiDisplayStatus對象,在前面我們介紹過這幾個變量的含義了,當然這幾個變量會從WifiDisplayListener的各個callback分別去改變自己的值。接著我們到MediaRouter中去看如何處理這個broadcastReceiver,前面我們已經講過了,WifiDisplayStatusChangedReceiver會接收這個broadcast,然后調用updateWifiDisplayStatus來更新狀態,我們稍后來看這部分的實現。回到WifiDisplayController的updateWfdEnableState方法中,接著會調用updateScanState方法開始掃描WifiDisplay設備:

?
1
2
3
4
5
6
7
8
9
private void updateScanState() {
????if (mScanRequested && mWfdEnabled && mDesiredDevice == null) {
????????if (!mDiscoverPeersInProgress) {
????????????Slog.i(TAG, "Starting Wifi display scan.");
????????????mDiscoverPeersInProgress = true;
????????????handleScanStarted();
????????????tryDiscoverPeers();
????????}
????}


handleScanStarted用于通知WifiDisplayAdapter掃描開始了,當然WifiDisplayAdapter也會發broadcast給MediaRouter。接著會調用tryDiscoverPeers:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
private void tryDiscoverPeers() {
????mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {
????????@Override
????????public void onSuccess() {
????????????if (DEBUG) {
????????????????Slog.d(TAG, "Discover peers succeeded.? Requesting peers now.");
????????????}
????????????if (mDiscoverPeersInProgress) {
????????????????requestPeers();
????????????}
????????}
????mHandler.postDelayed(mDiscoverPeers, DISCOVER_PEERS_INTERVAL_MILLIS);
}


這里調用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去獲取所有掃描到的設備列表,下面來看這個函數的實現:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void requestPeers() {
????mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() {
????????@Override
????????public void onPeersAvailable(WifiP2pDeviceList peers) {
????????????if (DEBUG) {
????????????????Slog.d(TAG, "Received list of peers.");
????????????}
????????????mAvailableWifiDisplayPeers.clear();
????????????for (WifiP2pDevice device : peers.getDeviceList()) {
????????????????if (DEBUG) {
????????????????????Slog.d(TAG, "? " + describeWifiP2pDevice(device));
????????????????}
????????????????if (isWifiDisplay(device)) {
????????????????????mAvailableWifiDisplayPeers.add(device);
????????????????}
????????????}
????????????if (mDiscoverPeersInProgress) {
????????????????handleScanResults();
????????????}
????????}
????});
}


首先從掃描的設備列表中過濾掉不能做wifi display的設備,主要從三個方面過濾,一是純粹的P2P設備,不會待用WfdInfo;第二是帶有WfdInfo,但是暫時沒有被enable;三是只能是PrimarySinkDevice,看起來Android還不支持SecondSink。并將過濾掉剩下的設備加入到mAvailableWifiDisplayPeers列表中,接著調用handleScanResults來組裝WifiDisplay列表數組并notify給WifiDisplayAdapter:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void handleScanResults() {
????final int count = mAvailableWifiDisplayPeers.size();
????final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count);
????for (int i = 0; i < count; i++) {
????????WifiP2pDevice device = mAvailableWifiDisplayPeers.get(i);
????????displays[i] = createWifiDisplay(device);
????????updateDesiredDevice(device);
????}
????mHandler.post(new Runnable() {
????????@Override
????????public void run() {
????????????mListener.onScanResults(displays);
????????}
????});
}


這里首先根據mAvailableWifiDisplayPeers的數目創建一個WifiDisplay數組,然后一個個構造WifiDisplay對象,WifiDiplay對象包含以下幾個變量:

mDeviceAddress設備的Mac地址
mDeviceName設備的名字
mDeviceAlias設備的別名,一般為NULL
mIsAvailable是否可用狀態
mCanConnectWfdInfo中的SessionAvailable是否為1
mIsRemembered是否被記錄的


接著調用updateDesiredDevice用于判斷掃描到的這個設備是否是現在正在連接或者連接上的設備,如果是,則更新它的一些信息,以后在連接Wifi display的時候再來分析這一塊。接著就會向WifiDisplayAdapter回調onScanResults,回調函數中帶有已經掃描到的wifi display設備列表(如果有):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
????????public void onScanResults(WifiDisplay[] availableDisplays) {
????????????synchronized (getSyncRoot()) {
????????????????availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(
????????????????????????availableDisplays);
????????????????boolean changed = !Arrays.equals(mAvailableDisplays, availableDisplays);
????????????????// Check whether any of the available displays changed canConnect status.
????????????????for (int i = 0; !changed && i<br>
這里首先調用PersistentDateStore的applyWifiDisplayAliases方法去判斷掃描到的設備中有沒有以前連接過并記錄下來的wifi display設備,比較方法是比較兩者的MAC地址,如果在PersistentDateStore中找到,再比較兩者的別名(Alias),如果不相同則更新results列表,細節的代碼可以看applyWifiDisplayAlias中的實現。
<pre class="brush:java;">??? public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) {
????????WifiDisplay[] results = displays;
????????if (results != null) {
????????????int count = displays.length;
????????????for (int i = 0; i < count; i++) {
????????????????WifiDisplay result = applyWifiDisplayAlias(displays[i]);
????????????????if (result != displays[i]) {
????????????????????if (results == displays) {
????????????????????????results = new WifiDisplay[count];
????????????????????????System.arraycopy(displays, 0, results, 0, count);
????????????????????}
????????????????????results[i] = result;
????????????????}
????????????}
????????}
????????return results;
????}</pre><br>
回到上面的onScanResults中,接著判斷剛掃描到的設備列表(availableDisplays)和之前存儲的設備列表(mAvailableDisplays)之間有沒有變化,可以數組內容以及是否可連兩個方面檢查。如果有變化,則把剛掃描到的設備列表(availableDisplays)賦值給存儲的設備列表(mAvailableDisplays)。接下來調用fixRememberedDisplayNamesFromAvailableDisplaysLocked來更新PersistentDateStore中存儲的已經連接過的wifi
?display設備,更新的條件是設備的MAC地址一樣,但設備的DeviceName和DeviceAlias有變化,這是就要更新到PersistentDateStore中,代碼如下:
<pre class="brush:java;">??? private void fixRememberedDisplayNamesFromAvailableDisplaysLocked() {
????????boolean changed = false;
????????for (int i = 0; i < mRememberedDisplays.length; i++) {
????????????WifiDisplay rememberedDisplay = mRememberedDisplays[i];
????????????WifiDisplay availableDisplay = findAvailableDisplayLocked(
????????????????????rememberedDisplay.getDeviceAddress());
????????????if (availableDisplay != null && !rememberedDisplay.equals(availableDisplay)) {
????????????????mRememberedDisplays[i] = availableDisplay;
????????????????changed |= mPersistentDataStore.rememberWifiDisplay(availableDisplay);
????????????}
????????}
????????if (changed) {
????????????mPersistentDataStore.saveIfNeeded();
????????}
????}</pre>如果掃描到的設備列表中有wifi display設備的名字或者別名發生了變化,就會調用到PersistentDataStore.saveIfNeeded方法把數據寫到/data/system/display-manager-state.xml中。
<br>
回到onScanResults中,接下來會調用updateDisplaysLocked來更新返回給MediaRouter的設備列表信息,在這里會把掃描到的設備以及之前存儲下來的設備做一次合并,共同保存到mDisplays數組中,后面在發送broadcast的時候,就會把mDisplays保存到WifiDisplayStatus對象中,并在broadcast帶上這個對象。
<pre class="brush:java;">??? private void updateDisplaysLocked() {
????????List<wifidisplay> displays = new ArrayList<wifidisplay>(
????????????????mAvailableDisplays.length + mRememberedDisplays.length);
????????boolean[] remembered = new boolean[mAvailableDisplays.length];
????????for (WifiDisplay d : mRememberedDisplays) {
????????????boolean available = false;
????????????for (int i = 0; i < mAvailableDisplays.length; i++) {
????????????????if (d.equals(mAvailableDisplays[i])) {
????????????????????remembered[i] = available = true;
????????????????????break;
????????????????}
????????????}
????????????if (!available) {
????????????????displays.add(new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(),
????????????????????????d.getDeviceAlias(), false, false, true));
????????????}
????????}
????????for (int i = 0; i < mAvailableDisplays.length; i++) {
????????????WifiDisplay d = mAvailableDisplays[i];
????????????displays.add(new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(),
????????????????????d.getDeviceAlias(), true, d.canConnect(), remembered[i]));
????????}
????????mDisplays = displays.toArray(WifiDisplay.EMPTY_ARRAY);
????}</wifidisplay></wifidisplay></pre><br>
上面的實現中先從mRememberedDisplays逐個添加wifi display設備到displays數組中,如果在mAvailableDisplays有相同的設備,則不添加到displays數組;后面再把mAvailableDisplays所有元素添加到displays數組,并全部賦值給mDisplays數組。
<br>
再回到onScanResults中,就會調用scheduleStatusChangedBroadcastLocked向WifiDisplayHandler發送MSG_SEND_STATUS_CHANGE_BROADCAST消息,這個我們在前面已經講過了,然后會發送broadcast,并帶上一個WifiDisplayStatus對象。現在我們再到MediaRouter和WifiDisplaySettings中看如何處理這個broadcast,先來看MediaRouter如何解析WifiDisplayStatus對象。updateWifiDisplayStatus的實現如下:<br>
<pre class="brush:java;">??? static void updateWifiDisplayStatus(WifiDisplayStatus status) {
????????WifiDisplay[] displays;
????????WifiDisplay activeDisplay;
????????if (status.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {
????????????displays = status.getDisplays();
????????????activeDisplay = status.getActiveDisplay();
????????} else {
????????????displays = WifiDisplay.EMPTY_ARRAY;
????????????activeDisplay = null;
????????}
????????String activeDisplayAddress = activeDisplay != null ?
????????????????activeDisplay.getDeviceAddress() : null;
????????// Add or update routes.
????????for (int i = 0; i < displays.length; i++) {
????????????final WifiDisplay d = displays[i];
????????????if (shouldShowWifiDisplay(d, activeDisplay)) {
????????????????RouteInfo route = findWifiDisplayRoute(d);
????????????????if (route == null) {
????????????????????route = makeWifiDisplayRoute(d, status);
????????????????????addRouteStatic(route);
????????????????} else {
????????????????????String address = d.getDeviceAddress();
????????????????????boolean disconnected = !address.equals(activeDisplayAddress)
????????????????????????????&& address.equals(sStatic.mPreviousActiveWifiDisplayAddress);
????????????????????updateWifiDisplayRoute(route, d, status, disconnected);
????????????????}
????????????????if (d.equals(activeDisplay)) {
????????????????????selectRouteStatic(route.getSupportedTypes(), route, false);
????????????????}
????????????}
????????}
????????// Remove stale routes.
????????for (int i = sStatic.mRoutes.size(); i-- > 0; ) {
????????????RouteInfo route = sStatic.mRoutes.get(i);
????????????if (route.mDeviceAddress != null) {
????????????????WifiDisplay d = findWifiDisplay(displays, route.mDeviceAddress);
????????????????if (d == null || !shouldShowWifiDisplay(d, activeDisplay)) {
????????????????????removeRouteStatic(route);
????????????????}
????????????}
????????}
????????sStatic.mPreviousActiveWifiDisplayAddress = activeDisplayAddress;
????}</pre>
<br>
上面的代碼中,首先從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設備。
<br>
我的一些簡單理解:MediaRouter只保存已經配對上的remote display設備,包括Wifi diplay、藍牙A2DP設備、chromecast設備等,用于提供給其它應用程序使用,比如youtube可以直接chromecast,當我們前面有成功和一個chromecast設備配對過后,youtube應用就可以從MediaRouter對象中獲取到當前已經配對的chromecast設備信息,并可以把youtube的視頻推送到chromecast上面播放;再舉個例子,百度視頻應用可以訪問MediaRouter中的wifi
?display設備,當我們設備中有已經連接或已經保存的wifi display設備時,就可以很方便的從直接百度視頻上面直接開始wifi display,而不需要用戶再去Settings里面掃描連接。
<br>
再來看WifiDisplaySettings中如何處理MSG_SEND_STATUS_CHANGE_BROADCAST:
<pre class="brush:java;">??? private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
????????@Override
????????public void onReceive(Context context, Intent intent) {
????????????String action = intent.getAction();
????????????if (action.equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {
????????????????scheduleUpdate(CHANGE_WIFI_DISPLAY_STATUS);
????????????}
????????}
????};</pre><br>
從MediaRouter中的callback消息也會進入到scheduleUpdate中,只是后面的參數不一樣,通過callback進來的參數是CHANGE_ROUTES,而broadcast進來的參數是CHANGE_WIFI_DISPLAY_STATUS,來看scheduleUpdate,最終實現是mUpdateRunnable<strong>中:</strong>
<pre class="brush:java;">??? private void update(int changes) {
????????boolean invalidateOptions = false;
????????// Update wifi display state.
????????if ((changes & CHANGE_WIFI_DISPLAY_STATUS) != 0) {
????????????mWifiDisplayStatus = mDisplayManager.getWifiDisplayStatus();
????????????// The wifi display feature state may have changed.
????????????invalidateOptions = true;
????????}
????????// Rebuild the routes.
????????final PreferenceScreen preferenceScreen = getPreferenceScreen();
????????preferenceScreen.removeAll();
????????// Add all known remote display routes.
????????final int routeCount = mRouter.getRouteCount();
????????for (int i = 0; i < routeCount; i++) {
????????????MediaRouter.RouteInfo route = mRouter.getRouteAt(i);
????????????if (route.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) {
????????????????preferenceScreen.addPreference(createRoutePreference(route));
????????????}
????????}
????????// Additional features for wifi display routes.
????????if (mWifiDisplayStatus != null
????????????????&& mWifiDisplayStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {
????????????// Add all unpaired wifi displays.
????????????for (WifiDisplay display : mWifiDisplayStatus.getDisplays()) {
????????????????if (!display.isRemembered() && display.isAvailable()
????????????????????????&& !display.equals(mWifiDisplayStatus.getActiveDisplay())) {
????????????????????preferenceScreen.addPreference(new UnpairedWifiDisplayPreference(
????????????????????????????getActivity(), display));
????????????????}
????????????}
????????}
????}</pre><br>
上面的代碼比較簡單,一個是從MediaRouter中獲取mRoutes數組中存著的remote display設備;一個是從broadcast中的WifiDisplayStatus對象中獲取mDisplay數組,兩者相互合并構建整個listview展現給用戶。至此,wifi display的掃描流程就介紹完了,下面是整體的流程圖:
<img src="http://www.2cto.com/uploadfile/Collfiles/20140405/201404050

轉載于:https://www.cnblogs.com/senior-engineer/p/4971396.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/375359.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/375359.shtml
英文地址,請注明出處:http://en.pswp.cn/news/375359.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

mvc controller跳轉頁面方法

1、直接Redirect后加 Controller/Action Response.Redirect("/User/Edit"); // return Redirect("/User/Edit"); return RedirectToAction("about","Home"); Response.Redirect("/User/Edit"); 2、直接r…

Win32ASM學習[13]:移位指令SHL,SHR,SAL,SAR,ROL,ROR,RCL,RCR,SHLD,SHRD

一. SHL、SHR、SAL、SAR: 移位指令 ---------------------------------------------------------------------------------------------------- ;SHL(Shift Left): 邏輯左移 ;SHR(Shift Right): 邏輯右移 ;SAL(Shift Arithmetic Left): 算術左移 ;SAR(Shift Ari…

angular中的表單驗證

angular中的表單驗證很強大&#xff0c; 一共有5中驗證信息&#xff0c;$valid,$invalid,$pristine,$dirty,$error. $valid-----當驗證通過的時候&#xff0c;為true,不通過的時候為false $invalid----當驗證不通過的時候&#xff0c;為true&#xff0c;通過的時候為true $pris…

Cortex-A15 Memory Hierarchy

ARM 平臺為實現速度和成本的平衡&#xff0c;使用多個層次的內存架構。對于多核 CPU 組成的 SOC&#xff0c;每個CPU 內部都有一組高速緩存&#xff0c;包含&#xff1a;ICache、DCache 和 TLB。多個 CPU 共享一個更大的 L2 緩存。L2緩存再和 CPU 外部的DDR3 內存交互。ICache …

Win32ASM學習[14]:符號擴展指令: CBW,CWDE,CDQ,CWD

----------------------------------------------------------------------------------------------------------------------------------------------------------------- ;CBW(Convert Byte to Word): 將 AL 擴展為 AX ;CWDE(Convert Word to Extended Double): 將 …

Win32ASM學習[15]:加減指令: INC、DEC、NEG、ADD、ADC、SUB、SBB、CMP

------------------------------------------------------------------------------------------------------------------------------------------------------------------- ;INC(Increment): 加一 ;DEC(Decrement): 減一 ;NEG(Negate): 求補(求反) ;ADD(Add): 加 ;A…

UINavigationController的簡單使用

UINavigationController的使用步驟初始化UINavigationController設置UIWindow的rootViewController為UINavigationController根據具體情況&#xff0c;通過push方法添加對應個數的子控制器UINavigationController的子控制器UINavigationController以棧的形式保存子控制器proper…

Android 圖片的縮略圖

<1>簡介 之前往往是通過Bitmap、Drawable和Canvas配合完成&#xff0c;需要寫一系列繁雜的邏輯去縮小原有圖片&#xff0c;從而得到縮略圖。 現在我給大家介紹一種比較簡單的方法&#xff1a;&#xff08;網上有&#xff09; 在Android 2.2版本中&#xff0c;新增了一個T…

Win32ASM學習[16] :乘除指令: MUL、IMUL、DIV、IDIV

---------------------------------------------------------------------------------------------------------------------------------------------------------------------- 一. 無符號數乘法指令 MUL(MULtiply) 格式: MUL OPRD 功能: 乘法操作. 說明: 1. OPRD為通用…

Linux命令之man

一、引言 一直聽別人說linux下有個男人很厲害&#xff0c;遇到不會的不懂的都可以問他&#xff0c;自己也會幾個man命令&#xff0c;但是一直不知道到底man命令是怎么用的&#xff0c;比如查詢不同類的幫助該在man后跟數字幾&#xff0c;man后的數字有什么具體的意思&#xff0…

《JavaScript權威指南》學習筆記 第二天 下好一盤大棋

前段學習js的時候總是零零散散的&#xff0c;以至于很多東西都模棱兩可。時間稍微一久&#xff0c;就容易忘記。最主要的原因是這些東西&#xff0c;原來學的時候就不是太懂&#xff0c;以至于和其他知識無法形成記憶鏈&#xff0c;所以孤零零的知識特別容易忘記。重溫犀牛書&a…

Win32ASM學習[17]:條件跳轉

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 一.跳轉相關的標志位: 11 10 9 8 7 6 5 4 3 2 1 0 OF DF IF TF SF Z…

SUID或SGID程序中能不能用system函數

system()函數的聲明和說明如下&#xff1a; 注意它的描述那里&#xff0c;system()執行一個由command參數定義的命令&#xff0c;通過調用/bin/sh -c命令來實現這個功能。也就是說它的邏輯是這樣的&#xff01; 進程調用system函數&#xff0c;system函數調用fork創建一個子進程…

史上最全的maven pom.xml文件教程詳解

<project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd >; <!-- 父項目的坐標。如果項目中沒有規定某個元素的值&am…

Win32ASM學習[18]:串指令-MOVS*、CMPS*、SCAS*、LODS*、REP、REPE、REPNE

-------------------------------------------------------------------------------------------------------------------------------------------------------- 一.字符串傳送指令 MOVSB/MOVSW/MOVSD 格式: MOVS OPD,OPS 功能: OPD<--OPS. 說明: 1. 其中OPS為源串符…

Xamarin iOS編寫第一個應用程序創建工程

Xamarin iOS編寫第一個應用程序創建工程 在Xcode以及Xamarin安裝好后&#xff0c;就可以在Xamarin Studio中編寫程序了。本節將主要講解在Xamarin Studio中如何進行工程的創建以及編寫代碼等內容XamariniOS編寫第一個應用程序創建工程本文選自Xamarin iOS開發實戰大學霸。 1.3.…

Jade之Extends

Extends jade允許多個jade文件繼承一個jade文件。 jade&#xff1a; //- layout.jade doctype html htmlheadblock titletitle Default titlebodyblock content //- index.jade extends ./layout.jadeblock titletitle Article Titleblock contenth1 My Article html&#xff1…

Win32ASM學習[19]:結構與聯合

結構和聯合分別用 struct、union 定義, 都是 ends 結束定義. 它們使用方法相同, 并可以互相嵌套; 主要區別是后者的各成員共用同一個地址. -------------------------------------------------------------------------------------------------------------------------------…

hdu 1257 最少攔截系統(貪心)

題意&#xff1a; 最少需要多少個攔截系統才能將所有的導彈攔截下來。 思路&#xff1a; 第1枚導彈一定需要第一個攔截系統&#xff0c;第2枚導彈如果比第1個高度高&#xff0c;則需要第二個攔截系統。 考慮第i枚導彈&#xff0c;如果前i-1枚導彈的高度都比它小&#xff0c;則需…

Birt使用總結

把report放到其他服務器要重新建立Data Source ,這是配置&#xff0c;拷貝項目時不會同時拷貝 (1)在EXTJs中利用Report實現報表的刷新 Ext.getCmp("showview").body.update("<iframe idshowviewframe src" "> </iframe>"…