WIFI DIRECT, CONNECTIONS WITHOUT USER INTERACTION

As  I wrote in the UX with Wifi Direct: The connection acceptance dialog article, there is acceptance dialog, which requires User interaction before any data can be exchanged between the devices.

This is not really ideal situation with Thali project, in which we would actually require the connections do be established fully automatically.  To fix this I would have two solutions which might work, depending on requirements:

  1. Teach each device, all “Known groups”, so they never ask for the dialog again
  2. Modify the communications to be handled in a way that no dialogs are ever shown.

For the first fix, we could make an app, which you would start and select which mode it would work (either Advertiser who waits for connections, or a devise that initiates the connecting  process), and the app would then do connection and once its verified to work be exchanging some data, they would revert the roles and do new round. While running both devices should have the dialog shown once, and user would then need to click the accept button to store the information in “Known Groups” settings.

This of course would be required to be handled with each device we can potentially have connections between, and would need to be re-done , in case the device is cleaned.

The second fix would require doing the connection a bit differently, in essence the steps are:

  1.  Use createGroup() fucntion from WifiP2pManager to create a group. This creates an access point, with random SSID and password.
    • Start same time a server which accepts the incoming connections required for your service
  2.  Once the access point is ready, you’ll receive WIFI_P2P_CONNECTION_CHANGED_ACTION. Then fetch the group information to get the SSID & password
  3. Create local service to advertise the access point. You can use the local instance variable for giving the access point information (SSID, password & IP-Address)
  4. Do Service Discovery to find any peers nearby advertising their access points. Once found, see what SSID, password and IP-Address are used with there.
  5.  Use WifiManager for forming the connection (instead of WifiP2pManager )
  6. Once the Connection is established,
    1. Stop advertising for the access point.
    2. Remove the access point
    3. Stop searching for additional access points
  7. Make connection to the IP-Address you got from the service you discovered in step 4.

Here’s coupld of pointers to remember with this approach

  • Its likely that all devices get same IP address when they form the access point, and thus do remember to remove the access point in step 6, otherwise you’ll communicate with your own server in step 7.
  • With Android devices, you can have connection only to one WLAN Access point, thus if you had any active WLAN connections, they will be disconnected when you start connecting in step 5.

I made simple example showing how the fix number two works, and you can find it from Github under the DrJukka/MyWifiMesh. Do note that its not fully finalized, and is just used for proof of concept for further development.

Advertisements

Android平台Wifi_Direct使用

Wifi_Direct是目前设备间最快的无线数据连接方式,速度可以达到40Mb/s。Google从Android 4.0(ICS)开始支持Wifi_Direct,而三星则更早些就在它自己的设备上支持了Wifi_Direct。几年来,Wifi_Direct的发展一直不温不火,但是目前市面上支持Wifi_Direct的设备并不是很多。
        从目前接触过得设备来看,三星I9100的Wifi_Direct功能其实使用了Wifi的硬件,所以,它在使用Wifi_Direct功能时,无法使用wifi;nexus7、Padfone infinite(A80)则有独立的硬件来支持Wifi_Direct,所以,在使用Wifi_Direct功能的时候,Wifi仍旧可用。
          Android framework提供了一个android.net.wifi.p2p包来提供对于Wifi_Direct的支持,其中包含了7个class和9个interface。其中WifiP2pManager为最核心的class,其他的class和interface都为它所用。
          使用Wifi_P2p需要的Permission有两个:

public static final String ACCESS_WIFI_STATE
Added in API level 1
Allows applications to access information about Wi-Fi networks
Constant Value: “android.permission.ACCESS_WIFI_STATE”
public static final String CHANGE_WIFI_STATE
Added in API level 1
Allows applications to change Wi-Fi connectivity state
Constant Value: “android.permission.CHANGE_WIFI_STATE”

Wifi_Direct的大致配对流程如下:

        1. WifiP2pManager.discoverPeers()开始扫描设备
        2. 获取扫描到的设备,选择其中一个设备进行连接配对WifiP2pManager.connect
        3. 配对成功后,根据WifiP2pInfo.isGroupOwner和WifiP2pInfo.groupOwnerAddress进行连接。
        流程图如下:
        个人认为Wifi_Direct配对需要注意的问题:
        1. Setting中启用/关闭WifiP2p按钮,应该是和Wifi的启用/关闭按钮放在一起了(其实,有些设备的实现中,Wifip2p使用的就是wifi的硬件),所以使用WifiP2p功能需要开启Wifi。
        2. Setting中BlueTooth有一个“让自己可见”的按钮,而Wifi_Direct没有这样的设置,仅提供了一个启动scan的按钮。本人尚未明确在未启动scan的情况下,设备对于其他wifi_direct是否是可见的,但是可以明确scan中的wifi_direct设备对其他设备来说是可见的。所以,建议需要进行配对的两台Wifi_Direct设备都进行scan。
        3. 配对成功的前提条件是:进行配对的两台设备都必须能够扫描到对方。所以,两台设备都进行scan操作的根本原因在这里。
        4. 开发者无法决定GroupOwner是哪台设备,但是可以通过WifiP2pConfig.groupOwnerIntent参数进行建议。
        从测试的结果来说,Wifi_Direct的表现受具体设备的影响很大,配对的速度也有较大差异,从10秒到2分钟甚至更久。大概的来说,nexus7成功的概率较高,个人感觉可以达到70%的成功率,Padfone infinite(A80)的成功率在50%以下。
         为了兼容传统的Wifi设备,Wifi_Direct其实还存在另一种使用方式,暂且称为兼容模式。兼容模式的特点在于,只需要担任GroupOwner的设备支持Wifi_Direct,而其他设备只需要支持传统的Wifi就可以了(个人觉得其实这种使用模式很像Android的便携热点功能)。
         操作流程为:
         1. 支持Wifi_Direct的设备创建group,WifiP2pManager.createGroup(),成为GroupOwner。
         2.  其他设备扫描Wifi_Direct设备创建group后产生的Wifi热点并连接即可。
         兼容模式存在的一个问题是:因为作为group member的设备是使用Wifi硬件接入到group中,所以会导致member进行wifi 热点切换以及网络中断,可能对正在进行的网络操作造成影响,而group owner则不存在这个问题。另外,而WifiP2p配对的使用方式,WifiP2p和Wifi可以独立运作,相互不受影响。
          但是,兼容模式因为省去了扫描和配对的过程,所以建立连接的成功率明显提升,并且建立连接的速度要快不少(具体时间比较随机)。
          从个人的使用感觉来讲,这WifiP2p这套API接口高度的异步化,API都需要以回调的方式获取操作结果(包内interface比较多的原因就在于此)。更加麻烦的是,几个关键API(例如WifiP2pManager.connect)的回调获取到的结果仅仅是执行是否开始,真正的结果还得注册broadcast receiver,通过监听广播来获得,才能进行下一步操作。异步的设计提高了代码的逻辑复杂度。
         使用NFC来实现WifiP2p的连接:
         1. 使用NFC将owner设备创建的group的SSID和密码传递给member设备
         2. owner开始监听指定端口,等待member的连接
         3. member接收到nfc传递过来的数据后,根据SSID和密码连接到group
         4. 连接成功以后,过去owner设备的ip地址(获取gateway ip即可),连接到owner的指定端口
          常见问题:
          1. WifiP2p相关的广播有哪些,各自有哪些参数?
WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION:当WifiP2p扫描开始或者停止时,触发该广播
该广播包含一个int型extra, key为WifiP2pManager.EXTRA_DISCOVERY_STATE,其值为WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED或者WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED.
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ATIONIC:当WifiP2p状态发生变化时触发(如果WifiP2p可用,那么当BroadcastReceiverregister时,也会收到该广播)
该广播包含一个int型extra,key为WifiP2pManager.EXTRA_WIFI_STATE,其值为WifiP2pManager.WIFI_P2P_STATE_ENABLED或者WifiP2pManager.WIFI_P2P_STATE_DISABLED。
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION:当设备的WifiP2p状态发生变化时触发广播(如果WifiP2p可用,那么当BroadcastReceiverregister时,也会收到该广播)
该广播包含一个类型为WifiP2pDevice的extra,key为WifiP2pManager.EXTRA_WIFI_P2P_DEVICE.
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION:当WifiP2p扫描时,发现device列表发生变化时,触发该广播
该广播不含extra,开发者应该接收到此广播后,调用WifiP2pManager.requestPeers()函数查询当前设别列表。
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION:当WifiP2p的group发生变化时,触发该广播。
该广播包含两个extra:
key:WifiP2pManager.EXTRA_NETWORK_INFO,其值为NetworkInfo类型。
key:WifiP2pManager.EXTRA_P2P_INFO,其值为WifiP2pInfo类型。
PS:这里的WifiP2p group发生变化包含如下情况:
1. 建立group
2. member加入到group
3. member退出group
4. 关闭group
        2. 如何获得WifiP2pGroupInfo,它有什么用?
WifiP2pManager.requestGroupInfo()函数,可以获取GroupInfo,较为有用的api有:
1. GroupInfo.getClientList()可以获得连接到group的member列表
2. GroupInfo.getNetWorkName()可以获得group的wifi热点名称(SSID)
3. GroupInfo.getPassphrase() 可以获得连接到wifi 热点的密码
        3. 如何获得WifiP2pInfo?
可以从WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION广播中的extra中获取
也可以从WifiP2pManager.requestConnectionInfo()函数获取。
        4. 如何防止配对产生的提示框?
在不修改framework的情况下,本人暂时为找到可行的方案。
这个提示狂是由系统提供的,具体表现视设备而定。nexus只在第一次配对的时候弹出,而A80每一次配对都会弹出。
但是,使用兼容模式使用Wifi_Direct是没有提示框的。
         5. 如何实现wifi热点的连接?
 经过测试,在A80上,如下代码可以实现连接到热点。

  1. // build a wifi config
  2. final WifiConfiguration config = new WifiConfiguration();
  3. config.allowedAuthAlgorithms.clear();
  4. config.allowedPairwiseCiphers.clear();
  5. config.allowedGroupCiphers.clear();
  6. config.allowedKeyManagement.clear();
  7. config.allowedProtocols.clear();
  8. config.SSID = “\”” + ssid + “\””;<span style=“color:#009900;”>//设定ssid</span>
  9. config.preSharedKey = “\”” + pw + “\””;<span style=“color:#009900;”>//设定密码</span>
  10. config.hiddenSSID = false;
  11. config.status = WifiConfiguration.Status.ENABLED;
  12. config.priority = 1;
  13. config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
  14. config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
  15. config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
  16. config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
  17. config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
  18. config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
  19. config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
  20. config.allowedPairwiseCiphers.set(3);
  21. config.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
  22. config.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
  23. // connect to ap
  24. int id = WifiManager.addNetwork(config);
  25. config.networkId = id;
  26. if (id != –1 && mWifiManager.enableNetwork(id, true)) {

What Is DHCP?

Dynamic Host Configuration Protocol (DHCP) is a client/server protocol that automatically provides an Internet Protocol (IP) host with its IP address and other related configuration information such as the subnet mask and default gateway. RFCs 2131 and 2132 define DHCP as an Internet Engineering Task Force (IETF) standard based on Bootstrap Protocol (BOOTP), a protocol with which DHCP shares many implementation details. DHCP allows hosts to obtain required TCP/IP configuration information from a DHCP server.

Windows Server® 2008 includes the DHCP Server service, which is an optional networking component. All Windows-based clients include the DHCP client as part of TCP/IP, including Windows Vista®, the Windows Server®°2003 operating system, the Windows® XP Professional operating system, Microsoft Windows®°2000 Professional operating system, Microsoft Windows°NT® Workstation°4.0 operating system, Microsoft Windows® Millennium Edition operating system, and the Microsoft Windows®°98 operating system.

Every device on a TCP/IP-based network must have a unique unicast IP address to access the network and its resources. Without DHCP, IP addresses for new computers or computers that are moved from one subnet to another must be configured manually; IP addresses for computers that are removed from the network must be manually reclaimed.

With DHCP, this entire process is automated and managed centrally. The DHCP server maintains a pool of IP addresses and leases an address to any DHCP-enabled client when it starts up on the network. Because the IP addresses are dynamic (leased) rather than static (permanently assigned), addresses no longer in use are automatically returned to the pool for reallocation.

The network administrator establishes DHCP servers that maintain TCP/IP configuration information and provide address configuration to DHCP-enabled clients in the form of a lease offer. The DHCP server stores the configuration information in a database that includes:

  • Valid TCP/IP configuration parameters for all clients on the network.
  • Valid IP addresses, maintained in a pool for assignment to clients, as well as excluded addresses.
  • Reserved IP addresses associated with particular DHCP clients. This allows consistent assignment of a single IP address to a single DHCP client.
  • The lease duration, or the length of time for which the IP address can be used before a lease renewal is required.

A DHCP-enabled client, upon accepting a lease offer, receives:

  • A valid IP address for the subnet to which it is connecting.
  • Requested DHCP options, which are additional parameters that a DHCP server is configured to assign to clients. Some examples of DHCP options are Router (default gateway), DNS Servers, and DNS Domain Name. For a full list of DHCP options, see DHCP Tools and Options.

In Windows Server 2008, the DHCP Server service provides the following benefits:

  • Reliable IP address configuration. DHCP minimizes configuration errors caused by manual IP address configuration, such as typographical errors, or address conflicts caused by the assignment of an IP address to more than one computer at the same time.
  • Reduced network administration. DHCP includes the following features to reduce network administration:
    • Centralized and automated TCP/IP configuration.
    • The ability to define TCP/IP configurations from a central location.
    • The ability to assign a full range of additional TCP/IP configuration values by means of DHCP options.
    • The efficient handling of IP address changes for clients that must be updated frequently, such as those for portable computers that move to different locations on a wireless network.
    • The forwarding of initial DHCP messages by using a DHCP relay agent, which eliminates the need for a DHCP server on every subnet.

Wi-Fi Direct vs. Bluetooth

Wi-Fi Direct (formerly Wi-Fi P2P) is appearing in more and more smartphones these days and despite not having used it so far for anything useful I thought I’d investigate a bit more to see how it works and how it differs from Bluetooth in addition to the obviously higher data transfer rates.

Unlike many other Wi-Fi functionalities, Wi-Fi Direct is not specified by the IEEE. Instead, the Wi-Fi Alliance, best known for it’s Wi-Fi certification program and logo, was responsible for the feature. It’s not their first one, they were also the driving force to fix the WEP encryption issue many years ago with WPA and later WPA2. Also, they have defined the set of rules for Wireless Multi Media (WMM) and other options in the IEEE standards to ensure the implementation of a minimum set of features and interoperability between devices.

In essense the Wi-Fi direct feature is straight forward. While traditional Wi-Fi networks require an access point for devices to communicate with each other, Wi-Fi direct allows two devices to communicate with each other without a dedicated access point. Instead, one of the two devices assumes the role of the access point and becomes the Group Owner (GO) of the Wi-Fi direct network. Other devices, even non-Wi-Fi direct devices, can then join this group as the GO behaves just like a standard access point.  This means that the GO device also includes DHCP functionality to assign IP addresses to clients of the group network.

Once the Wi-Fi connection is established and an IP address has been assigned the standard TCP/IP protocol stack is used to transfer data between devices. And this is the biggest difference between Bluetooth and Wi-Fi direct. While Bluetooth defines profiles for transferring images, business cards, calendar entries, audio signals, etc., Wi-Fi direct itself only offers a transparent IP channel. To transfer data between two devices, compatible apps are required.

The advantage of this approach is that those apps can work in Wi-Fi direct networks and also in traditional Wi-Fi environments. A TV, for example, that is capable of traditional Wi-Fi and Wi-Fi direct can run a server application to receive pictures and video streams over both Wi-Fi variants. The home owner would stream his material over the traditional Wi-Fi network while visitors would use Wi-Fi direct to skip the somewhat complicated process of joining the local wireless infrastructure network.

The disadvantage of this approach in the example above is that the visitor has to first download a client app that can communicate with the server app on the television. While this might be acceptable for the scenario above, it’s too complicated for just exchanging a few images, files or contacts on the go between two smartphones. Perhaps Google will add such apps to Android in the future to make this easier but this wouldn’t help transferring files to the iPhone, Blackberries, Windows Phone, etc. This is where Bluetooth still shines due to its standardized profiles which are implemented on many operating systems. This is a bit unfortunate as transferring multimedia content between different mobile operating systems would definitely benefit from fast Wi-Fi transmissions due to ever increasing file sizes and the practical transfer speeds of Bluetooth of only around 2 Mbit/s.

In practice, Wi-Fi direct has arrived in Android smartphones today but is not really noticed so far. The Android OS offers an API for apps from version 4 of the OS to scan for Wi-Fi direct devices and then to establish a connection. That offers interesting possibilities for ad-hoc communication, such as for example local multi-player games. Think kids in cars on a long road trip…

Service vs intent service

In short, a Service is a broader implementation for the developer to set up background operations, while an IntentService is useful for “fire and forget” operations, taking care of background Thread creation and cleanup.

From the docs:

Service A Service is an application component representing either an application’s desire to perform a longer-running operation while not interacting with the user or to supply functionality for other applications to use.

IntentService IntentService is a base class for Services that handle asynchronous requests(expressed as Intents) on demand. Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.

When to use?

  • The Service can be used in tasks with no UI, but shouldn’t be too long. If you need to perform long tasks, you must use threads within Service.
  • The IntentService can be used in long tasks usually with no communication to Main Thread. If communication is required, can use Main Thread handler or broadcast intents. Another case of use is when callbacks are needed (Intent triggered tasks).

How to trigger?

  • The Service is triggered by calling method startService().
  • The IntentService is triggered using an Intent, it spawns a new worker thread and the method onHandleIntent() is called on this thread.

Triggered From

  • The Service and IntentService may be triggered from any thread, activity or other application component.

Runs On

  • The Service runs in background but it runs on the Main Thread of the application.
  • The IntentService runs on a separate worker thread.

Limitations / Drawbacks

  • The Service may block the Main Thread of the application.
  • The IntentService cannot run tasks in parallel. Hence all the consecutive intents will go into the message queue for the worker thread and will execute sequentially.

When to stop?

  • If you implement a Service, it is your responsibility to stop the service when its work is done, by calling stopSelf() or stopService(). (If you only want to provide binding, you don’t need to implement this method).
  • The IntentService stops the service after all start requests have been handled, so you never have to call stopSelf().

Android网络通信之WiFi Direct

使用Wi-Fi Direct技术可以让具备硬件支持的设备在没有中间接入点的情况下进行直接互联。Android 4.0(API版本14)及以后的系统都提供了对Wi-Fi Direct的API支持。通过对这些API的使用,开发者可以实现支持Wi-Fi Direct的设备间进行相互探测和连接,从而获得较之蓝牙更远距离的高速数据通信效果。对于诸如多人游戏、图片共享等需要在用户之间传输数据的应用而言,这一技术无疑是十分有价值的。
关于Wi-Fi Direct的API函数的使用需要注意一下几个要点:
·用于探测(discover)对等设备(peers)、向对等设备发起请求(request)以及建立连接(connect)的方法定义在类WifiP2pManager中。
·通过设置监听器(Listener)可以获知WifiP2pManager中方法调用的成功与否。监听器以参数的形式传递给被调用的方法。
·当发现新对等设备或链接丢失的时候,Wi-Fi Direct系统(framework)以意向(Intent)的方式根据检测到的不同事件做出相应的通知。
开发中,以上三点的配合使用相当普遍。简单举个例子,定义一个监听器WifiP2pManager.ActionListener并调用函数discoverPeers(),当相应事件发生的时候就会在ActionListener.onSuccess()和ActionListener.onFailure()两个方法中得到通知。当discoverPeers()方法检测到了对等设备列表变化的时候,可以收到由系统广播(broadcast)发出一个WIFI_P2P_PEERS_CHANGED_ACTION意向。
创建广播接收器以处理Wi-Fi Direct意向的基本步骤如下:
1、创建一个继承BroadcastReceiver类的新类。构造函数的参数分别传递WifiP2pManager,WifiP2pManager.Channel,以及在这个广播接收器中需要注册的活动(activity)。这是一种最常见的参数设置模式,它让广播接收器能够引起活动作出更新,同时又能在必要时使用Wi-Fi硬件和通信信道。
2、在广播接收器的onReceive()函数中,针对感兴趣的特定意向可以执行相应的动作(actions)。例如,当广播接收器收到了意向WIFI_P2P_PEERS_CHANGED_ACTION,就可以调用requestPeers()方法来列举出当前探测到的对等设备。
创建Wi-Fi Direct应用:
完整的Wi-Fi Direct应用包含创建并注册广播接收器、检测对等设备、连接对等设备以及在对等设备间传输数据几个方面的功能。下面将详细介绍如何实现。
准备工作:
在使用Wi-Fi Direct API之前,首先要确保应用程序能够访问硬件,并且设备支持Wi-Fi Direct协议。如果这些条件都满足,就可以获取一个WifiP2pManager实例,创建并注册广播接收器,最后就是使用Wi-Fi Direct API了。
在Android manifest文件中加入以下内容,允许使用Wi-Fi设备上的硬件并声明应用程序正确支持了调用API所需的最低SDK版本。
<uses-sdk android:minSdkVersion=”14″ />
<uses-permission android:name=”android.permission.ACCESS_WIFI_STATE” />
<uses-permission android:name=”android.permission.CHANGE_WIFI_STATE” />
<uses-permission android:name=”android.permission.CHANGE_NETWORK_STATE” />
<uses-permission android:name=”android.permission.INTERNET” />
<uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE” />
检查Wi-Fi Direct支持并已开启。推荐在广播接收器收到WIFI_P2P_STATE_CHANGED_ACTION意向的时候进行检测。检测结果需要通告相应的活动并做出处理:
@Override
public void onReceive(Context context, Intent intent) {

String action = intent.getAction();
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
// Wifi Direct is enabled
} else {
// Wi-Fi Direct is not enabled
}
}

}
在活动的onCreate()方法中获取WifiP2pManager对象的一个实例,通过该对象的initialize()方法向Wi-Fi Direct系统注册当前的应用程序。注册成功后,会返回一个WifiP2pManager.Channel,通过它,应用程序就能和Wi-Fi Direct系统交互。WifiP2pManager和WifiP2pManager.Channel对象以及一个活动的引用最后都被作为参数传递给自定义的广播接收器。这样,该活动就能够响应广播接收器的通知并作出相应的更新。当然,这样做也使程序具备了操纵设备Wi-Fi状态的能力:
WifiP2pManager mManager;
Channel mChannel;
BroadcastReceiver mReceiver;
……
@Override
protected void onCreate(Bundle savedInstanceState){

mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
mChannel = mManager.initialize(this, getMainLooper(), null);
mReceiver = new WiFiDirectBroadcastReceiver(mManager, mChannel, this);

}
创建一个意向过滤器(intent filter),其中添加的意向种类和广播接收器中的保持一致:
IntentFilter mIntentFilter;
……
@Override
protected void onCreate(Bundle savedInstanceState){
……
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
……
}
在活动的onResume()方法中注册广播接收器,并在活动的onPause()方法中注销它:
/* register the broadcast receiver with the intent values to be matched */
@Override
protected void onResume() {
super.onResume();
registerReceiver(mReceiver, mIntentFilter);
}
/* unregister the broadcast receiver */
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mReceiver);
}
一旦成功获取WifiP2pManager.Channel并创建了广播接收器,应用程序就已经具备了使用Wi-Fi Direct相关函数和接收Wi-Fi Direct意向的能力。尽管放手使用WifiP2pManager为你提供的方法,让程序也拥有Wi-Fi Direct的特殊能力吧!
探测对等设备(Discovering peers)
调用discoverPeers()函数可以探测到有效距离内的对等设备。它是一个异步函数,调用成功与否会在程序所创建WifiP2pManager.ActionListener监听器的onSuccess()和onFailure()中给出通知。值得注意的是,onSuccess()方法只会对成功探测到对等设备这一事件做出通知,而并不会提供任何关于已发现的对等设备的具体信息:
manager.discoverPeers(channel, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {

}
@Override
public void onFailure(int reasonCode) {

}
});
当成功检测到对等设备存在的时候,系统会广播WIFI_P2P_PEERS_CHANGED_ACTION意向。程序接收到该意向后,通过调用requestPeers()方法,就能获得已经探测到对等设备的清单。下面代码将展示如何实现这一过程:
PeerListListener myPeerListListener;
……
if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
/* request available peers from the wifi p2p manager. This is an asynchronous call and the calling activity is notified with a callback on PeerListListener.onPeersAvailable()  */
if (manager != null) {
manager.requestPeers(channel, myPeerListListener);
}
}
requestPeers()方法同样是一个异步函数,当它准备好一份对等设备列表的时候,就会通知监听器WifiP2pManager.PeerListListener中定义的onPeersAvailable()方法。而onPeersAvailable()方法中所能获取到的对等设备列表以WifiP2pDeviceList形式存储,通过遍历这个列表可以选择出希望连接的设备。
连接对等设备(Connecting to peers)
确定了要连接的设备,还需调用connect()方法建立连接。该方法的其中一个参数是WifiP2pConfig对象,它提供了要连接设备的相关信息。连接的成功与否需要通过监听器WifiP2pManager.ActionListener获取通知。下面的代码将示范如何建立设备连接:
//obtain a peer from the WifiP2pDeviceList
WifiP2pDevice device;
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = device.deviceAddress;
manager.connect(channel, config, new ActionListener() {
@Override
public void onSuccess() {
//success logic
}
@Override
public void onFailure(int reason) {
//failure logic
}
});
传输数据(Transferring data)
连接一旦建立成功,数据传输也就是顺理成章的事情。以下是通过socket发送数据的基本步骤:
1、创建ServerSocket。它将被用于监听特定端口,等待客户端发起的连接请求。该操作需要在后台线程中实现。
2、创建客户端Socket。客户端通过ServerSocket对应的IP和端口连接到服务设备。
3、客户端向服务器发生数据。客户socket成功连接到服务socket后,就能以字节流的形式向服务器发生数据了。
4、服务器socket通过accept()方法等待客户端数据连接的到来。该方法在收到客户端数据之前一直处于阻塞状态。因此,需要在单独的线程中调用它。数据连接一旦建立,服务设备就能接收到客户端的数据。这时要做的就是施以相应的动作,例如将数据保存到文件,或者是直接显示到用户界面上,等等。
以下代码修改自SDK自带的示例Wi-Fi Direct Demo。它演示了如何建立一对客户端-服务器连接,并由客户端向服务器发送JPEG图片。若需完整的演示工程,只需编译并运行SDK示例Wi-Fi Direct Demo即可。
public static class FileServerAsyncTask extends AsyncTask {
private Context context;
private TextView statusText;
public FileServerAsyncTask(Context context, View statusText) {
this.context = context;
this.statusText = (TextView) statusText;
}
@Override
protected String doInBackground(Void… params) {
try {
/* Create a server socket and wait for client connections. This call blocks until a connection is accepted from a client */
ServerSocket serverSocket = new ServerSocket(8888);
Socket client = serverSocket.accept();
/*If this code is reached, a client has connected and transferred data Save the input stream from the client as a JPEG file */
final File f = new File(Environment.getExternalStorageDirectory() + “/”
+ context.getPackageName() + “/wifip2pshared-”
+ System.currentTimeMillis()  + “.jpg”);
File dirs = new File(f.getParent());
if (!dirs.exists())
dirs.mkdirs();
f.createNewFile();
InputStream inputstream = client.getInputStream();
copyFile(inputstream, new FileOutputStream(f));
serverSocket.close();
return f.getAbsolutePath();
} catch (IOException e) {
Log.e(WiFiDirectActivity.TAG, e.getMessage());
return null;
}
}
// Start activity that can handle the JPEG image
@Override
protected void onPostExecute(String result) {
if (result != null) {
statusText.setText(“File copied – ” + result);
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(“file://” + result), “image/*”);
context.startActivity(intent);
}
}
}
On the client, connect to the server socket with a client socket and transfer data. This example transfers a JPEG file on the client device’s file system:
Context context = this.getApplicationContext();
String host;
int port;
int len;
Socket socket = new Socket();
byte buf[]  = new byte[1024];

try {
/* Create a client socket with the host,  port, and timeout information.*/
socket.bind(null);
socket.connect((new InetSocketAddress(host, port)), 500);
/* Create a byte stream from a JPEG file and pipe it to the output stream  of the socket. This data will be retrieved by the server device. */
OutputStream outputStream = socket.getOutputStream();
ContentResolver cr = context.getContentResolver();
InputStream inputStream = null;
inputStream = cr.openInputStream(Uri.parse(“path/to/picture.jpg”));
while ((len = inputStream.read(buf)) != -1) {
outputStream.write(buf, 0, len);
}
outputStream.close();
inputStream.close();
} catch (FileNotFoundException e) {
//catch logic
} catch (IOException e) {
//catch logic
}
/* Clean up any open sockets when done transferring or if an exception occurred. */
finally {
if (socket != null) {
if (socket.isConnected()) {
try {
socket.close();
} catch (IOException e) {
//catch logic
}
}
}
}

Android – Broadcast Receivers

Broadcast Receivers simply respond to broadcast messages from other applications or from the system itself. These messages are sometime called events or intents. For example, applications can also initiate broadcasts to let other applications know that some data has been downloaded to the device and is available for them to use, so this is broadcast receiver who will intercept this communication and will initiate appropriate action.

There are following two important steps to make BroadcastReceiver works for the system broadcasted intents −

  • Creating the Broadcast Receiver.
  • Registering Broadcast Receiver

There is one additional steps in case you are going to implement your custom intents then you will have to create and broadcast those intents.

Creating the Broadcast Receiver

A broadcast receiver is implemented as a subclass of BroadcastReceiverclass and overriding the onReceive() method where each message is received as a Intent object parameter.

public class MyReceiver extends BroadcastReceiver {
   @Override
   public void onReceive(Context context, Intent intent) {
      Toast.makeText(context, "Intent Detected.", Toast.LENGTH_LONG).show();
   }
}

Registering Broadcast Receiver

An application listens for specific broadcast intents by registering a broadcast receiver in AndroidManifest.xml file. Consider we are going to register MyReceiver for system generated event ACTION_BOOT_COMPLETED which is fired by the system once the Android system has completed the boot process.

broadcast

BROADCAST-RECEIVER

<application
   android:icon="@drawable/ic_launcher"
   android:label="@string/app_name"
   android:theme="@style/AppTheme" >
   <receiver android:name="MyReceiver">
   
      <intent-filter>
         <action android:name="android.intent.action.BOOT_COMPLETED">
         </action>
      </intent-filter>
   
   </receiver>
</application>

Now whenever your Android device gets booted, it will be intercepted by BroadcastReceiver MyReceiver and implemented logic inside onReceive()will be executed.

There are several system generated events defined as final static fields in the Intent class. The following table lists a few important system events.

Event Constant Description
android.intent.action.BATTERY_CHANGED Sticky broadcast containing the charging state, level, and other information about the battery.
android.intent.action.BATTERY_LOW Indicates low battery condition on the device.
android.intent.action.BATTERY_OKAY Indicates the battery is now okay after being low.
android.intent.action.BOOT_COMPLETED This is broadcast once, after the system has finished booting.
android.intent.action.BUG_REPORT Show activity for reporting a bug.
android.intent.action.CALL Perform a call to someone specified by the data.
android.intent.action.CALL_BUTTON The user pressed the “call” button to go to the dialer or other appropriate UI for placing a call.
android.intent.action.DATE_CHANGED The date has changed.
android.intent.action.REBOOT Have the device reboot.

Broadcasting Custom Intents

If you want your application itself should generate and send custom intents then you will have to create and send those intents by using thesendBroadcast() method inside your activity class. If you use thesendStickyBroadcast(Intent) method, the Intent is sticky, meaning theIntent you are sending stays around after the broadcast is complete.

public void broadcastIntent(View view)
{
   Intent intent = new Intent();
   intent.setAction("com.tutorialspoint.CUSTOM_INTENT");
   sendBroadcast(intent);
}

This intent com.tutorialspoint.CUSTOM_INTENT can also be registered in similar way as we have regsitered system generated intent.

<application
   android:icon="@drawable/ic_launcher"
   android:label="@string/app_name"
   android:theme="@style/AppTheme" >
   <receiver android:name="MyReceiver">
   
      <intent-filter>
         <action android:name="com.tutorialspoint.CUSTOM_INTENT">
         </action>
      </intent-filter>
   
   </receiver>
</application>

Example

This example will explain you how to create BroadcastReceiver to intercept custom intent. Once you are familiar with custom intent, then you can program your application to intercept system generated intents. So let’s follow the following steps to modify the Android application we created inHello World Example chapter −

Step Description
1 You will use Android studio to create an Android application and name it as My Application under a package com.example.My Application as explained in the Hello World Example chapter.
2 Modify main activity file MainActivity.java to add broadcastIntent()method.
3 Create a new java file called MyReceiver.java under the packagecom.example.My Application to define a BroadcastReceiver.
4 An application can handle one or more custom and system intents without any restrictions. Every indent you want to intercept must be registered in your AndroidManifest.xml file using <receiver…/> tag
5 Modify the default content of res/layout/activity_main.xml file to include a button to broadcast intent.
6 No need to modify the string file, Android studio take care of string.xml file.
7 Run the application to launch Android emulator and verify the result of the changes done in the application.

Following is the content of the modified main activity filesrc/com.example.My Application/MainActivity.java. This file can include each of the fundamental life cycle methods. We have addedbroadcastIntent() method to broadcast a custom intent.

package com.example.My Application;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.content.Intent;
import android.view.View;

public class MainActivity extends Activity {

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      }
   
   @Override
   public boolean onCreateOptionsMenu(Menu menu) {
      getMenuInflater().inflate(R.menu.activity_main, menu);
      return true;
      }
   
   // broadcast a custom intent. 
   public void broadcastIntent(View view){
      Intent intent = new Intent();
      intent.setAction("com.tutorialspoint.CUSTOM_INTENT");
      sendBroadcast(intent);
   }
}

Following is the content of src/com.example.My Application/MyReceiver.java:

package com.example.My Application;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class MyReceiver extends BroadcastReceiver {
   @Override
   public void onReceive(Context context, Intent intent) {
      Toast.makeText(context, "Intent Detected.", Toast.LENGTH_LONG).show();
   }
}

Following will the modified content of AndroidManifest.xml file. Here we have added <service…/> tag to include our service:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.example.My Application"
   android:versionCode="1"
   android:versionName="1.0" >
   
   <uses-sdk
      android:minSdkVersion="8"
      android:targetSdkVersion="22" />
   
   <application
       android:icon="@drawable/ic_launcher"
       android:label="@string/app_name"
       android:theme="@style/AppTheme" >
       
       <activity
          android:name=".MainActivity"
          android:label="@string/title_activity_main" >
       
          <intent-filter>
             <action android:name="android.intent.action.MAIN" />
             <category android:name="android.intent.category.LAUNCHER"/>
          </intent-filter>
          
       </activity>
       
       <receiver android:name="MyReceiver">
       
       <intent-filter>
          <action android:name="com.tutorialspoint.CUSTOM_INTENT">
          </action>
       </intent-filter>
       
       </receiver>
       
   </application>
</manifest>

Following will be the content of res/layout/activity_main.xml file to include a button to broadcast our custom intent −

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
   android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
   android:paddingRight="@dimen/activity_horizontal_margin"
   android:paddingTop="@dimen/activity_vertical_margin"
   android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
   
   <TextView
      android:id="@+id/textView1"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Example of Broadcast"
      android:layout_alignParentTop="true"
      android:layout_centerHorizontal="true"
      android:textSize="30dp" />
      
   <TextView
      android:id="@+id/textView2"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Tutorials point "
      android:textColor="#ff87ff09"
      android:textSize="30dp"
      android:layout_above="@+id/imageButton"
      android:layout_centerHorizontal="true"
      android:layout_marginBottom="40dp" />
      
   <ImageButton
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:id="@+id/imageButton"
      android:src="@drawable/abc"
      android:layout_centerVertical="true"
      android:layout_centerHorizontal="true" />
      
   <Button
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:id="@+id/button2"
      android:text="Broadcast Intent"
      android:onClick="broadcastIntent"
      android:layout_below="@+id/imageButton"
      android:layout_centerHorizontal="true" />

</RelativeLayout>

Following will be the content of res/values/strings.xml to define two new constants −

<resources>    
    <string name="menu_settings">Settings</string>
    <string name="title_activity_main">My Application</string>
</resources>

Let’s try to run our modified Hello World! application we just modified. I assume you had created your AVD while doing environment set-up. To run the app from Android studio, open one of your project’s activity files and click Run Eclipse Run Icon icon from the tool bar. Android Studio installs the app on your AVD and starts it and if everything is fine with your set-up and application, it will display following Emulator window −

Android Broadcast Demo

Now to broadcast our custom intent, let’s click on Broadcast Intent button, this will broadcast our custom intent “com.tutorialspoint.CUSTOM_INTENT”which will be intercepted by our registered BroadcastReceiver i.e. MyReceiver and as per our implemented logic a toast will appear on the bottom of the the simulator as follows −

Android Broadcast Intent

You can try implementing other BroadcastReceiver to intercept system generated intents like system boot up, date changed, low battery etc.