Android now allows your app to display unique content on additional screens that are connected to the user’s device over either a wired connection or Wi-Fi. To create unique content for a secondary display, extend the Presentation
class and implement the onCreate()
callback. Within onCreate()
, specify your UI for the secondary display by callingsetContentView()
. As an extension of the Dialog
class, the Presentation
class provides the region in which your app can display a unique UI on the secondary display.
To detect secondary displays where you can display your Presentation
, use either the DisplayManager
or MediaRouter
APIs. While the DisplayManager
APIs allow you to enumerate multiple displays that may be connected at once, you should usually use MediaRouter
instead to quickly access the system’s default display for presentations.
To get the default display for your presentation, call MediaRouter.getSelectedRoute()
and pass it ROUTE_TYPE_LIVE_VIDEO
. This returns a MediaRouter.RouteInfo
object that describes the system’s currently selected route for video presentations. If the MediaRouter.RouteInfo
is not null, call getPresentationDisplay()
to get theDisplay
representing the connected display.
You can then display your presentation by passing the Display
object to a constructor for your Presentation
class. Your presentation will now appear on the secondary display.
To detect at runtime when a new display has been connected, create an instance of MediaRouter.SimpleCallback
in which you implement the onRoutePresentationDisplayChanged()
callback method, which the system will call when a new presentation display is connected. Then register the MediaRouter.SimpleCallback
by passing it to MediaRouter.addCallback()
along with the ROUTE_TYPE_LIVE_VIDEO
route type. When you receive a call to onRoutePresentationDisplayChanged()
, simply call MediaRouter.getSelectedRoute()
as mentioned above.
To further optimize the UI in your Presentation
for secondary screens, you can apply a different theme by specifying the android:presentationTheme
attribute in the <style>
that you’ve applied to your application or activity.
Keep in mind that screens connected to the user’s device often have a larger screen size and likely a different screen density. Because the screen characteristics may different, you should provide resources that are optimized specifically for such larger displays. If you need to request additional resources from your Presentation
, call getContext()
.getResources()
to get the Resources
object corresponding to the display. This provides the appropriate resources from your app that are best suited for the secondary display's screen size and density.
For more information and some code samples, see the Presentation
class documentation.
好了,接下来首先来看一看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层详细介绍,以后有机会一并再结合源码分析一下。
简单来说,DisplayManager 可以列举出可以直连显示的多个设备,MediaRouter提供了快速获得系统中用于演示(presentations)默认显示设备的方法。可以利用 frameworks/base/media/java/android/media/MediaRouter.java 下的 getSelectedRoute(int type){ } 函数来获得当前所选择type类型的Router信息。对于PresentationWithMediaRouterActivity应用而言,
MediaRouter mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO); Display presentationDisplay = route != null ? route.getPresentationDisplay() : null;可以看到这里传入的是ROUTE_TYPE_LIVE_VIDEO类型,供其获取已选择的route信息。之后,则是判断route信息是否为空,如果不为空则返回被选择演示(presentation)设备。值得一提的是,该方法只对 route信息类型为ROUTE_TYPE_LIVE_VIDEO有效。
接下来,只要将该Display对象作为自己重构的演示(Presentation)类构造函数参数传入,这样自己重构的演示就会出现在第二个显示设备上。
mPresentation = new DemoPresentation(this, presentationDisplay); //传入上面构造的presentationDisplay ... try { mPresentation.show(); //显示 } catch (WindowManager.InvalidDisplayException ex) { Log.w(TAG, "Couldn't show presentation! Display was removed in " + "the meantime.", ex); mPresentation = null; } } ...
private final static class DemoPresentation extends Presentation { ... public DemoPresentation(Context context, Display display) { super(context, display); } ... }
为了进一步优化附加显示设备自定义演示UI的显示效果,你可以在<style>属性中指定相关应用主题为android:presentationTheme。
为了在运行时检测外设显示设备的连接状态,你需要在自己的实现类中创建一个 MediaRouter.SimpleCallback的一个实例,该实例中需要自己实现onRoutePresentationDisplayChanged() 等回调函数。当一个新的演示显示设备连接时,系统就会回调该函数,进一步其就会调用上面提到的MediaRouter.getSelectedRoute()函数。
private final MediaRouter.SimpleCallback mMediaRouterCallback = new MediaRouter.SimpleCallback() { public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { updatePresentation(); } public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { updatePresentation(); } public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) { updatePresentation(); } };当然,使用者需要使用MediaRouter.addCallback()函数完成注册,如同在PresentationWithMediaRouterActivity应用中,
mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_LIVE_VIDEO, mMediaRouterCallback); //完成注册
这里可以简单看看调用流程,首先可以看到onRoutePresentationDisplayChanged()回调函数在MediaRouter.java会先触发dispatchRoutePresentationDisplayChanged()函数,
frameworks/base/media/java/android/media/MediaRouter.java
static void dispatchRoutePresentationDisplayChanged(RouteInfo info) { for (CallbackInfo cbi : sStatic.mCallbacks) { if ((cbi.type & info.mSupportedTypes) != 0) { cbi.cb.onRoutePresentationDisplayChanged(cbi.router, info); } } }
进一步可以看到该分发函数被用于更新演示设备的状态,并将其提供给frameworks/base/core/java/android/app/Presentation.java中注册的监听函数,
frameworks/base/media/java/android/media/MediaRouter.java
private void updatePresentationDisplays(int changedDisplayId) { final Display[] displays = getAllPresentationDisplays(); final int count = mRoutes.size(); for (int i = 0; i < count; i++) { final RouteInfo info = mRoutes.get(i); Display display = choosePresentationDisplayForRoute(info, displays); //根据displays的地址信息从所有显示类型为Presentation displays的设备中选择对应的显示设备 if (display != info.mPresentationDisplay || (display != null && display.getDisplayId() == changedDisplayId)) { info.mPresentationDisplay = display; dispatchRoutePresentationDisplayChanged(info); } } }
@Override public void onDisplayAdded(int displayId) { updatePresentationDisplays(displayId); } @Override public void onDisplayChanged(int displayId) { updatePresentationDisplays(displayId); } @Override public void onDisplayRemoved(int displayId) { updatePresentationDisplays(displayId); }frameworks/base/core/java/android/app/Presentation.java
@Override protected void onStart() { super.onStart(); mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);// Presentation线程一启动就会注册Display Manager中负责监听演示设备变化的三个监听器 ... }
private final DisplayListener mDisplayListener = new DisplayListener() { //三个监听器——负责监听演示设备变化 @Override public void onDisplayAdded(int displayId) { } @Override public void onDisplayRemoved(int displayId) { if (displayId == mDisplay.getDisplayId()) { handleDisplayRemoved(); } } @Override public void onDisplayChanged(int displayId) { if (displayId == mDisplay.getDisplayId()) { handleDisplayChanged(); } } };
该注册函数的实现实际是在DisplayManagerGlobal类中,该类主要负责管理显示管理器(Display Manager)与显示管理服务(Display Manager Service)之间的通信。
frameworks/base/core/java/android/hardware/display/DisplayManagerGlobal.java
public void registerDisplayListener(DisplayListener listener, Handler handler) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } synchronized (mLock) { int index = findDisplayListenerLocked(listener); if (index < 0) { mDisplayListeners.add(new DisplayListenerDelegate(listener, handler)); //给动态数组中增添显示监听处理代理 registerCallbackIfNeededLocked(); //实际负责注册回调函数的方法 } } }
private void registerCallbackIfNeededLocked() { if (mCallback == null) { mCallback = new DisplayManagerCallback(); try { mDm.registerCallback(mCallback); } catch (RemoteException ex) { Log.e(TAG, "Failed to register callback with display manager service.", ex); mCallback = null; } } }可以看到,registerCallbackIfNeededLocked()函数中新建的回调函数实际上是IDisplayManagerCallback的AIDL接口实现,
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { @Override public void onDisplayEvent(int displayId, int event) { if (DEBUG) { Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event); } handleDisplayEvent(displayId, event); } }frameworks/base/core/java/android/hardware/display/IDisplayManagerCallback.aidl
private void registerCallbackIfNeededLocked() { if (mCallback == null) { mCallback = new DisplayManagerCallback(); try { mDm.registerCallback(mCallback); } catch (RemoteException ex) { Log.e(TAG, "Failed to register callback with display manager service.", ex); mCallback = null; } } }
之后则是将新建的mCallback作为参数传入IDisplayManager中的registerCallback(in IDisplayManagerCallback callback);接口函数中。
最后,来看看与IDisplayManager AIDL接口对应的Service实现。
frameworks/base/services/java/com/android/server/display/DisplayManagerService.java
@Override // Binder call public void registerCallback(IDisplayManagerCallback callback) { if (callback == null) { throw new IllegalArgumentException("listener must not be null"); } synchronized (mSyncRoot) { int callingPid = Binder.getCallingPid(); if (mCallbacks.get(callingPid) != null) { throw new SecurityException("The calling process has already " + "registered an IDisplayManagerCallback."); } CallbackRecord record = new CallbackRecord(callingPid, callback); try { IBinder binder = callback.asBinder(); binder.linkToDeath(record, 0); } catch (RemoteException ex) { // give up throw new RuntimeException(ex); } mCallbacks.put(callingPid, record); } }可以看到该服务采取同步机制,这是因为DisplayManager可能同时被多个线程访问,这里所有属于display manager的对象都会使用同一把锁。本服务将该锁称为同步root,其有唯一的类型DisplayManagerService.SyncRoot,所有需要该锁的方法都会以“Locked"作为后缀。另外,CallbackRecord函数会绑定IDisplayManagerCallback接口。并且通过函数notifyDisplayEventAsync( )向回调函数提供显示事件通知,事件类型分为三类EVENT_DISPLAY_ADDED、EVENT_DISPLAY_CHANGED以及EVENT_DISPLAY_REMOVED等。
函数notifyDisplayEventAsync( )会在DisplayManager处理线程DisplayManagerHandler中,当msg类型为MSG_DELIVER_DISPLAY_EVENT时被函数deliverDisplayEvent( )调用。进一步而言,类型为MSG_DELIVER_DISPLAY_EVENT的消息,是由函数void sendDisplayEventLocked(int displayId, int event)发送的。该函数的使用者addLogicalDisplayLocked()以及 updateLogicalDisplaysLocked( )正好通过sendDisplayEventLocked( )将显示设备ID displayid 与 三种显示事件类型联系在了一起。最后正是函数handleDisplayDeviceAdded( )、handleDisplayDeviceChanged()以及handleDisplayDeviceRemoved()将显示设备的变化状态通过注册在显示管理服务中的监听器DisplayAdapter.Listener以异步方式传递给Display adapter。
private final class DisplayAdapterListener implements DisplayAdapter.Listener { @Override public void onDisplayDeviceEvent(DisplayDevice device, int event) { switch (event) { case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED: handleDisplayDeviceAdded(device); break; case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED: handleDisplayDeviceChanged(device); break; case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED: handleDisplayDeviceRemoved(device); break; } } ... }这样将服务与显示设备适配器分离的做法有两方面优点,其一方面简洁的封装了两个类的不同职责:显示适配器负责处理各个显示设备,而显示管理服务则负责处理全局的状态变化;另一方面,其将会避免在异步搜索显示设备时导致的死锁问题。
接下来,让我们进入正题,来通过WiFi Display Setting应用来大致分析一下Wifidisplay具体的流程,其在源码目录下
packages/apps/Settings/src/com/android/settings/wfd/WifiDisplaySettings.java
关于此应用的细节这里就不再详述,我们首先来看Wifi Display 的设备发现,这里首先需要查看Wifi Display的设备状态,通过调用getFeatureState()可以获得进行该操作的设备是否可以支持Wifi Display,以及该功能是否能被用户使能用信息。如果此时的设备状态表示Widfi display功能已经开启,那么就开始进行设备发现
if (mWifiDisplayStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) { //检查设备状态 mDisplayManager.scanWifiDisplays(); }在搜寻完设备后,用户可以选择设备进行连接,当然正在进行连接或已经连接配对的设备,再次点击配置后,会弹出对话框供用户选择断开连接。
public boolean onPreferenceTreeClick ( PreferenceScreen preferenceScreen, Preference preference) { if (preference instanceof WifiDisplayPreference) { WifiDisplayPreference p = (WifiDisplayPreference)preference; WifiDisplay display = p.getDisplay(); if (display.equals(mWifiDisplayStatus.getActiveDisplay())) { showDisconnectDialog(display); } else { mDisplayManager.connectWifiDisplay(display.getDeviceAddress()); } } return super.onPreferenceTreeClick(preferenceScreen, preference); }最后,该应用还提供对设备重命名以及剔除Wifi Display 设备连接历史信息的方法。
首先,从应用层的设备发现来往下分析,我们容易看到,如同对上面PresentationWithMediaRouterActivity应用的分析,这里采取的也是AIDL进程间通信方式。来看一看接口定义文件,
frameworks/base/core/java/android/hardware/display/IDisplayManager.aidl
package android.hardware.display; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.WifiDisplay; import android.hardware.display.WifiDisplayStatus; import android.view.DisplayInfo; /** @hide */ interface IDisplayManager { DisplayInfo getDisplayInfo(int displayId); int[] getDisplayIds(); void registerCallback(in IDisplayManagerCallback callback); // No permissions required. void scanWifiDisplays(); // Requires CONFIGURE_WIFI_DISPLAY permission to connect to an unknown device. // No permissions required to connect to a known device. void connectWifiDisplay(String address); // No permissions required. void disconnectWifiDisplay(); // Requires CONFIGURE_WIFI_DISPLAY permission. void renameWifiDisplay(String address, String alias); // Requires CONFIGURE_WIFI_DISPLAY permission. void forgetWifiDisplay(String address); // No permissions required. WifiDisplayStatus getWifiDisplayStatus(); }
可以看到,该接口中定义了DisplayManger所需要交互的全部函数,包括设备发现和设备连接等函数。DisplayManager是根据DisplayManagerGlobal提供的单实例来访问相应的接口函数,并与Display manager service建立起联系。以下是DisplayManagerGlobal提供的获取其单例模式的函数,
frameworks/base/core/java/android/hardware/display/DisplayManagerGlobal.java
public static DisplayManagerGlobal getInstance() { synchronized (DisplayManagerGlobal.class) { if (sInstance == null) { IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE); if (b != null) { sInstance = new DisplayManagerGlobal(IDisplayManager.Stub.asInterface(b)); //获取DISPLAY_SERVICE服务代理并用于填充构造函数 } } return sInstance; } } private final IDisplayManager mDm; // AIDL接口对象 private DisplayManagerGlobal(IDisplayManager dm) { mDm = dm; } public void scanWifiDisplays() { try { mDm.scanWifiDisplays(); } catch (RemoteException ex) { Log.e(TAG, "Failed to scan for Wifi displays.", ex); } }frameworks/base/core/java/android/hardware/display/DisplayManager.java
public DisplayManager(Context context) { mContext = context; mGlobal = DisplayManagerGlobal.getInstance(); } public void scanWifiDisplays() { mGlobal.scanWifiDisplays(); }
接下来,再看一看scanWifiDisplays()在Display Manager Service中的实现,也就是AIDL接口的实际实现。
frameworks/base/services/java/com/android/server/display/DisplayManagerService.java
public final class DisplayManagerService extends IDisplayManager.Stub { ... @Override // Binder call public void scanWifiDisplays() { final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { if (mWifiDisplayAdapter != null) { mWifiDisplayAdapter.requestScanLocked(); } } } finally { Binder.restoreCallingIdentity(token); } } ... }以上程序使用了mWifiDisplayAdapter对象,WifiDisplayAdapter类继承于显示适配器(DisplayAdapter),该类负责处理完成在连接到Wifi-display设备时与媒体服务、SurfaceFlinger以及显示管理服务之间的各种交互及操作。在继续分析WifiDisplayAdapter中的流程前,我们先来看看系统是启动该服务的大致流程。
首先在ServerThread.run中通过addService(Context.DISPLAY_SERVICE,…)来注册该服务
frameworks/base/services/java/com/android/server/SystemServer.java
display = new DisplayManagerService(context, wmHandler, uiHandler); ServiceManager.addService(Context.DISPLAY_SERVICE, display, true);
通过display.systemReady(safeMode,onlyCore)来初始化。
当系统属性persist.debug.wfd.enable为1或config_enableWifiDisplay为1,并且不是内核模式和安全模式才进行初始化。该服务是displays的全局管理者,决定如何根据当前链接的物理显示设备来配置逻辑显示器。当状态发生变化时发送通知给系统和应用程序,等等。包括的适配器有OverlayDisplayAdapter,WifiDisplayAdapter,HeadlessDisplayAdapter,LocalDisplayAdapter。对于WifiDisplayAdapter而言,流程大致如下图所示,
public void requestScanLocked() { if (DEBUG) { Slog.d(TAG, "requestScanLocked"); } getHandler().post(new Runnable() { @Override public void run() { if (mDisplayController != null) { mDisplayController.requestScan(); } } }); }
可以看到此函数又调用了WifiDisplayController类中的requestScan()方法,值得注意的是WifiDisplayController对象必须在handler线程中实例化,该类负责处理控制在WifiDisplayAdapter与WifiP2pManager之间的各种异步操作。
frameworks/base/services/java/com/android/server/display/WifiDisplayController.java
public void requestScan() { discoverPeers(); } private void discoverPeers() { if (!mDiscoverPeersInProgress) { mDiscoverPeersInProgress = true; mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES; //尝试发现配对设备次数,默认值为10 handleScanStarted(); tryDiscoverPeers(); } private void handleScanStarted() { mHandler.post(new Runnable() { @Override public void run() { mListener.onScanStarted(); //供WifiDisplayAdapter使用的监听器接口函数 } }); } private void tryDiscoverPeers() { mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() { //直接调用 //WifiP2pManager接口 @Override public void onSuccess() { ... mDiscoverPeersInProgress = false; requestPeers(); //获得P2P已经配对的设备在判断是否是Wifidisplay设备,如果是加入WifiP2pDevice动态数组中 } @Override public void onFailure(int reason) { ... if (mDiscoverPeersInProgress) { if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { mHandler.postDelayed(new Runnable() { @Override public void run() { if (mDiscoverPeersInProgress) { if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { mDiscoverPeersRetriesLeft -= 1; ... tryDiscoverPeers(); } else { handleScanFinished(); mDiscoverPeersInProgress = false; } } } }, DISCOVER_PEERS_RETRY_DELAY_MILLIS); //一次发现不成功后延时定长时间后继续尝试连接,递归函数 } else { handleScanFinished(); mDiscoverPeersInProgress = false; } } } }); }可以看到,这里直接调用了void discoverPeers(Channel c, ActionListener listener){}函数,该函数发起WIFI对等点发现,该函数会收到发现成功或失败的监听回调。发现过程会一直保持到连接初始化完成或者一个P2P组建立完成。另外,在WifiDisplayController.java中可以看到还注册了WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION这一intent,以确定当p2p peers更改时(即收到WIFI_P2P_PEERS_CHANGED_ACTION广播后),重新获取Wifi Display配对列表,并结束设备发现任务完成相应工作,该流程由函数requestPeers()完成
private void requestPeers() { mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() { @Override public void onPeersAvailable(WifiP2pDeviceList peers) { if (DEBUG) { Slog.d(TAG, "Received list of peers."); } mAvailableWifiDisplayPeers.clear(); for (WifiP2pDevice device : peers.getDeviceList()) { if (DEBUG) { Slog.d(TAG, " " + describeWifiP2pDevice(device)); } if (isWifiDisplay(device)) { //根据设备wfdInfo来判断其是否支持wifi display;并且判断其设备类型是否是主sink设备 mAvailableWifiDisplayPeers.add(device); } } handleScanFinished(); //结束设备发现,对所有符合要求的wifidisplay设备创建Parcelable对象 } }); }
类似与handleScanStarted()函数,这里结束设备发现任务并且完成相应处理工作的函数handleScanFinished(),也开启监听线程。这些监听线程将在WifiDisplayAdapter被注册使用,
frameworks/base/services/java/com/android/server/display/WifiDisplayAdapter.java
private final WifiDisplayController.Listener mWifiDisplayListener = new WifiDisplayController.Listener() { @Override public void onFeatureStateChanged(int featureState) { synchronized (getSyncRoot()) { if (mFeatureState != featureState) { mFeatureState = featureState; scheduleStatusChangedBroadcastLocked(); } } } @Override public void onScanStarted() { synchronized (getSyncRoot()) { if (mScanState != WifiDisplayStatus.SCAN_STATE_SCANNING) { mScanState = WifiDisplayStatus.SCAN_STATE_SCANNING; scheduleStatusChangedBroadcastLocked(); } } } @Override public void onScanFinished(WifiDisplay[] availableDisplays) { synchronized (getSyncRoot()) { availableDisplays = mPersistentDataStore.applyWifiDisplayAliases( availableDisplays); if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING || !Arrays.equals(mAvailableDisplays, availableDisplays)) { mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING; mAvailableDisplays = availableDisplays; scheduleStatusChangedBroadcastLocked(); } } } ... };可以看到,这些监听接口函数在触发时,都会调用同一个函数scheduleStatusChangedBroadcastLocked(),
private final WifiDisplayHandler mHandler; private void scheduleStatusChangedBroadcastLocked() { mCurrentStatus = null; if (!mPendingStatusChangeBroadcast) { mPendingStatusChangeBroadcast = true; mHandler.sendEmptyMessage(MSG_SEND_STATUS_CHANGE_BROADCAST); } } private final class WifiDisplayHandler extends Handler { public WifiDisplayHandler(Looper looper) { super(looper, null, true /*async*/); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_SEND_STATUS_CHANGE_BROADCAST: handleSendStatusChangeBroadcast(); break; ... } } } }函数scheduleStatusChangedBroadcastLocked()会向内类注册的Handler处理函数发送MSG_SEND_STATUS_CHANGE_BROADCAST消息,处理函数接收到该消息后由handleSendStatusChangeBroadcast()向设备上所有注册过ACTION_WIFI_DISPLAY_STATUS_CHANGED这一intent的接受者发送WifiDisplayStatus广播,
private void handleSendStatusChangeBroadcast() { final Intent intent; synchronized (getSyncRoot()) { if (!mPendingStatusChangeBroadcast) { return; } mPendingStatusChangeBroadcast = false; intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS, getWifiDisplayStatusLocked()); } // Send protected broadcast about wifi display status to registered receivers. getContext().sendBroadcastAsUser(intent, UserHandle.ALL); }
最后,我们来看看对于Wifi Display 设备发现最后需要注意的一个部分,即在WifidisplayController中调用的WifiP2pManager中的discoverPeers()接口函数。
frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
public void discoverPeers(Channel c, ActionListener listener) { checkChannel(c); c.mAsyncChannel.sendMessage(DISCOVER_PEERS, 0, c.putListener(listener)); }
当用户在搜寻设备时,该函数会向Channel中发送DISCOVER_PEERS信号,并注册监听器监听响应结果。Channel的初始化在WifiDisplayController的构造函数中由函数Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener){}完成,该函数将P2phandler连接到P2p处理函数框架中。当设备进入P2pEnabledState状态中,并且处理函数接受到DISCOVER_PEERS信号后,真正调用WifiNative的接口函数p2pFind(),并且执行wifi_command()函数调用Wifi设备底层的命令,
frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pService.java
clearSupplicantServiceRequest(); if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) { replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED); sendP2pDiscoveryChangedBroadcast(true); } else { replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED, WifiP2pManager.ERROR); }
如果p2pFind(int timeout)调用doBooleanCommand("P2P_FIND " + timeout)并且执行成功,则向先前连接的P2pHandler处理函数回复DISCOVER_PEERS_SUCCEEDED信号,并且调用监听函数回调接口((ActionListener) listener).onSuccess(),回调WifidisplayController中的discoverPeers()函数做发现设备成功后的获得设备列表工作即执行函数requestPeers()。最后,还会给在boot之前注册的接收者发送WIFI_P2P_DISCOVERY_CHANGED_ACTION广播。
至此,本文已经基本讲清楚了Wifi Display在设备发现时的基本调用流程,关于连接和开始传送数据流等内容将会在下一回的内容讨论。