Wi-Fi Direct and "normal" Wi-Fi - Different MAC? - android

I'm currently trying to connect two phones which know each other's MAC address via Wi-Fi Direct, and stumbled upon the following problem:
The MAC address, which I receive from
WifiManager wifiMan = (WifiManager) this
.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInf = wifiMan.getConnectionInfo();
MAC_ADDRESS = wifiInf.getMacAddress();
is slightly different than the one I receive from the WifiP2pManager when discovering and requesting peers. Example: a0:xx:xx:... turns into a2:xx:xx....
Does anyone know why? I did not find any way to get the "Wi-Fi Direct MAC address", and as I thought the MAC address should be unique, and it's the same Wi-Fi module that handles both (normal Wi-Fi and P2P/Direct). This is very weird..
What should I do? For the two devices (Galaxy Nexus) I've got, it's always only the first two characters that differ in the MAC addresses - should I simply discard them? Is the probability to encounter problems (two devices which only differ in the first part of MAC address) too high?
Thanks.

Reading about the MAC address on wikipedia.
Addresses can either be universally administered addresses or locally administered addresses.
Universally administered and locally administered addresses are distinguished by setting the second-least-significant bit of the most significant byte of the address. This bit is also referred to as the U/L bit, short for Universal/Local, which identifies how the address is administered. If the bit is 0, the address is universally administered. If it is 1, the address is locally administered.
Since Wi-Fi Direct is just another stack on top of MAC, you should also check what that bit can mean for it. I've found some mail discussion shedding some light on this. Apparently quote below is from a WFA spec.
The P2P Device shall assign a P2P Interface Address, corresponding to
the format as described in §7.1.3.3.1 of IEEE Std 802.11‑2007 1, which
is used to communicate with the P2P Group Owner or Clients within a P2P
Group. A P2P Interface Address is not required to be globally unique and
may be locally administered. A P2P Interface Address may be the same as
the P2P Device Address provided the requirements for P2P Interface
Address in this clause are satisfied.
So I believe answer to this question is, you shouldn't take MAC address from WifiManager and use it with Wi-Fi P2P connections.

I had been searching for this during my project. My requirements were to uniquely identify devices in an adhoc P2p network formed with WiFi Direct. Each device should identify its friend device the next time when it comes into proximity. I needed my own WiFi (Direct) MAC and my friends' to create a Key for this friend zone creation.
My Research: The design is in such a way that there is an Unique Universal ID and a Local ID. Reason: Universal ID can only be used to connect to Infrastructure mode Networks. Local ID could be used for "ad-hoc" mode networks(device to device). In this ad-hoc mode, there are possibilities that a single device might simultaneosly belong to several ad-hoc groups.
Hence to support this concurrent operations, P2p devices support
Multiple MAC entities, possibly on different channels.
For each session, a persistent group MAY use a different channel and device
MAC for each session.
P2P devices use their global MAC address as Device ID during discovery and negotiation, and a temporary local MAC address for all frames within a group. Understood from here
However, there is NO straight forward way to obtain one's own WiFi P2p MAC address. Issue 53437: Android.
In this issue discussion, the project member from google has suggested this is possible and just that it hasn't been documented
Solution: Using intent filter WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION and the extra
from the intent WifiP2pManager.EXTRA_WIFI_P2P_DEVICE
This is how I have used it in my project:
#Override
public void onReceive(Context context, Intent intent) {
....
....
String action = intent.getAction();
if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
.equals(action)) {
WifiP2pDevice device = (WifiP2pDevice) intent
.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
String myMac = device.deviceAddress;
Log.d(TAG, "Device WiFi P2p MAC Address: " + myMac);
/* Saving WiFi P2p MAC in SharedPref */
sharedPref = context.getSharedPreferences(context.getString(R.string.sp_file_name), Context.MODE_PRIVATE);
String MY_MAC_ADDRESS = sharedPref.getString(context.getString(R.string.sp_field_my_mac), null);
if (MY_MAC_ADDRESS == null || MY_MAC_ADDRESS != myMac) {
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(context.getString(R.string.sp_field_my_mac), myMac);
editor.commit();
}
Hope this helps someone!

iFixit says that the Galaxy Nexus uses the BCM4330 for its MAC, Baseband, and PHY, so if you have any friends at Broadcom you could ask them.
Sadly, the datasheet is not public; the best I can do is link you to a block diagram.

I was struggling all night to figure out a way to retrieve WiFi Direct mac address instead, since my requirements were drafted around the assumption that it's feasible.
It's kind of round about, you create a single device group and get the owner and the device address along with it.
Here's the code,
final WifiP2pManager p2pManager = (WifiP2pManager) getSystemService(WIFI_P2P_SERVICE);
final WifiP2pManager.Channel channel = p2pManager.initialize(this, getMainLooper(), null);
p2pManager.createGroup(channel, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
p2pManager.requestGroupInfo(channel, new WifiP2pManager.GroupInfoListener() {
#Override
public void onGroupInfoAvailable(WifiP2pGroup wifiP2pGroup) {
Log.i("", wifiP2pGroup.getOwner().deviceAddress);
// Following removal necessary to not have the manager busy for other stuff, subsequently
p2pManager.removeGroup(channel, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
Log.i("", "Removed");
}
#Override
public void onFailure(int i) {
Log.i("", "Failed " + i);
}
});
}
});
}
#Override
public void onFailure(int i) {
Log.i("", String.valueOf(i));
}
});

Forget Wifi manager. Wifi direct address does not equals to MAC address. Wifi direct address uses for wifi direct connection. You can not use to anything else.
To connect two device with wifi direct device, you have to create a WifiP2pGroup with one of your device. With your another device, you have to search the WifiP2pGroups, select yours, and connect.

You can get WiFi direct address using next code:
public String getWFDMacAddress(){
try {
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface ntwInterface : interfaces) {
if (ntwInterface.getName().equalsIgnoreCase("p2p0")) {
byte[] byteMac = ntwInterface.getHardwareAddress();
if (byteMac==null){
return null;
}
StringBuilder strBuilder = new StringBuilder();
for (int i=0; i<byteMac.length; i++) {
strBuilder.append(String.format("%02X:", byteMac[i]));
}
if (strBuilder.length()>0){
strBuilder.deleteCharAt(strBuilder.length()-1);
}
return strBuilder.toString();
}
}
} catch (Exception e) {
Log.d(TAG, e.getMessage());
}
return null;
}

Related

How to programmatically connect to Wifi with no internet Android (Xamarin)

My app needs to use a wifi network (without internet, thus android avoids it by default). And I want it to be simple to use (like Grandma doesn't have to be manually changing settings). This is just an personal IoT device so I want to use ConnectivityManager.BindProcessToNetwork(Android.Net.Network network). How do I get the Android.Net.Network associated with the currently connected wifi network so I can use BindProcessToNetwork?
//c#(Xamarin)
//my rudimentary attempt to get the connected wifi network:
var networks = ConnectivityManager.GetAllNetworks();
foreach (Network network in networks) {
NetworkCapabilities networkCability =ConnectivityManager.GetNetworkCapabilities(network);
if (networkCability.HasTransport(TransportType.Wifi))
{
currentWifiNetwork = network; // this is never reached
}
}
ConnectivityManager.BindProcessToNetwork( currentWifiNetwork );
Is there not a distinct Network object for all the phones currently in use WiFi, cellular, etc... networks?
This blog got me close: https://android-developers.googleblog.com/2016/07/connecting-your-app-to-wi-fi-device.html.
A binding socket method would work too (except the wifi network isn't an available network until the use network without internet box is checked). I just need to the App to use urls that are on port 8080 via the wifi.
I want to avoid having to manually telling Android to "use the network with no internet".
Cheers
Update
When I run this, there are only two Networks returned by ConnectivityManager.GetAllNetworks(), and looking at them in the debugger, one is the Cellular network with internet and mms, and the other is another Cellular network without internet and mms. So no ConnectivityManager.GetAllNetworks() doesn't get the wifi network as it appears android won't even add the wifi network unless it has internet! If the phones data is disabled Android will switch and use the internet-less wifi for all traffic (without having to check use the network anyways box).So their must be a way to get the WiFi network bound to the app! or...
How does one programmatically check the use network anyways box!?
I have not seen a solution to this. Just a bunch of open questions all over the web. At this rate I might just use dnsmasq on the iot device and a spoofing web server to make android think it has internet.
I also see that API 29 has NetworkBuilder and that you can specify a request for a WiFi network without internet capabilities...but I need lower API support.
Update:
Android now supports requesting local only wifi networks (networks without the NetworkCapabilities#NET_CAPABILITY_INTERNET capability) check out :
https://developer.android.com/reference/android/net/wifi/WifiNetworkSpecifier.Builder
Original Answer:
This is the solution that I came up with (for Api 28 ). It prioritizes WiFi over 4G/5G (Data) regardless of internet capability via a NetworkRequest, and then allows for both internet over 4G/5G (Data) and the app to use its local WiFi services:
public static WifiManager WifiManager { get; } = (WifiManager)Android.App.Application.Context.GetSystemService(Context.WifiService);
public static ConnectivityManager ConnectivityManager { get; set; } = (ConnectivityManager)Android.App.Application.Context.GetSystemService(Context.ConnectivityService);
public bool ConnectToWifi(string ssid, string password, bool previouslyConnected = true)
{
if (!WifiManager.IsWifiEnabled)
WifiManager.SetWifiEnabled(true); //turn on wifi if not on
var formattedSsid = $"\"{ssid}\"";
var formattedPassword = $"\"{password}\"";
var wifiConfig = new WifiConfiguration
{
Ssid = formattedSsid,
PreSharedKey = formattedPassword,
Priority = 0
};
_NetworkId = WifiManager.AddNetwork(wifiConfig);
WifiManager.Disconnect();
bool enableNetwork = WifiManager.EnableNetwork(_NetworkId, true);
NetworkRequest.Builder builder = new NetworkRequest.Builder(); //request that WiFi be prioritized over the 4G internet capable network.
builder.AddTransportType(TransportType.Wifi);
ConnectivityManager.RequestNetwork(builder.Build(), new BindNetworkCallBack ());
return enableNetwork;
}
This call back then binds the appropriate WIFI network to the app(process)! Allowing for the user to both simultaneously use the app with the local server over WIFI, and other apps that still require internet, as Android OS can allow other processes to access the internet via the 4G/5G data connection!
public class BindNetworkCallBack : ConnectivityManager.NetworkCallback
{
public override void OnAvailable(Network network)
{
if (WifiManager.ConnectionInfo.BSSID == NetworkBSSID) /*
The only way on Android (API 28+) to check if the acquired network is
the one you want is to use the BSSID (MAC address) of the network.
You can omit the if statement if you want to presume the acquired network is correct/
cannot know the MAC address...
*/
{
try
{
ConnectivityManager.BindProcessToNetwork(network);
}
catch (Exception ex)
{
Debug.WriteLine(#"\tERROR Unable to Bind process to network {0}", ex.Message);
}
}
}
}
"bindProcessToNetwork Binds the current process to network. All Sockets created in the future (and not explicitly bound via a bound SocketFactory from Network.getSocketFactory()) will be bound to network. All host name resolutions will be limited to network as well." - Android Docs

android - Connecting to a WiFi P2P device without discovering peers

I want to establish a WiFi-Direct Connection with another device peered through NFC. My steps are as follows:
First, Device A gets its own WiFiP2P address and transmits it to Device B via NFC.
Then, Device B tries to establish a connection with Device A using the provided address.
As you can see I didn't involve discovering peers in the process. But when Device B is trying to connect, the result is always failed (reason 0, this should be ERROR).
I think this might be related to device visibility, but I don't know and can't find any code to make a device visible.
Here's my code:
//NOTE: These code should be executed on Device B
//Starting WiFi Direct Transmission
//First we should establish a connection
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = remoteWifiP2pDevice;
//remoteWifiP2pDevice is the address of device A obtained from NFC
config.wps.setup = WpsInfo.PBC;
mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
//success logic
Toast.makeText(MainActivity.this, "success", Toast.LENGTH_SHORT).show();
if (!FILE_RECV)
{
new SendFilesTask().execute("");
}
}
#Override
public void onFailure(int reason) {
//failure logic
Toast.makeText(MainActivity.this, "failed" + reason, Toast.LENGTH_SHORT).show();
}
});
In OnCreate() I have
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);
mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
mChannel = mManager.initialize(this, getMainLooper(), null);
mReceiver = new WiFiDirectBroadcastReceiver(mManager, mChannel, this);
The WifiDirectBroadcastReceiver has code only related to getting Device A's address and can be considered empty.
So what's wrong with these and how can I fix it?
Thanks in advance.
P.S. If I connect Device A and B manually, and run my code again, it returns success.
WIfi Direct assumptions:
All the devices should be in discoverable (scanning mode)
simultaneously.
Devices scan for a 30 sec and after that
it stops by default.So we need to initiate scan programatically
using discoverpeers method.
Most important is displaying
nearby devices is device specific.ie sometimes devices wont show
nearby ones eventhough they are available.Thats why wifi direct
is not reliable and because of these, there wont be much wifi
direct apps in play store.
I have discovered that delaying several seconds will make the connection succeed. I don't know the reason, but this can be used as a workaround.
So after all is there a better solution to this? And why does the delay work?

How do I specify which interface (Wifi or Data) over which a UDP packet will be sent in Android?

I want the ability to specify and constrain the interface over which UDP datagrams will be sent.
I want to do this without physically disabling the "non wanted" interface. Assume standard INET connectivity is available over the chosen interface (in fact assume that if both interfaces are active then INET connectivity is available over boti)
Most mobile devices will send data over Wifi (in preference to Data interface) if the Wifi interface is enabled; but I want to know how to grammatically "force" UDP over my chosen interface.
There are similar questions already posted, but no question hits the topic precisely. Also I would like the programmed solution to be available (generically or via support library) as FAR BACK as possible in terms of Android SDK versions.
Please note that question "Android how to select wifi network interface to send udp packet" is old and was NEVER answered.
There is an issue with cyanide's answer - how does one ascertain the valid WiFi interface value associated with getName() for ALL devices, current and future?
The solution I have come up with would entail finding the IP address associated with the WiFi interface, then obtaining a UDP socket which is BOUND to this address. Getting a handle onto the WiFi interface can be done by accessing the WiFiManager:
WifiManager wifiManager =
(WifiManager) context.getApplicationContext ()
.getSystemService (Context.WIFI_SERVICE);
.....
int localInetAddressInt = wifiManager.getDhcpInfo ().ipAddress;
ByteBuffer tmp = ByteBuffer.allocate (4);
tmp.putInt (localInetAddressInt);
InetAddress localInetAddress = InetAddress.getByAddress (tmp.array ());
DatagramSocket socket = new DatagramSocket (portNumber, localInetAddress);
Then, when sending over the DatagramSocket (socket), the device SHOULD route the datagram over the appropriate interface, i.e. the interface associated with bound IP address, i.e. the WiFi interface to which the IP address is bound. The IP address would normally be associated with the WiFi interface by the WiFi Access Point via DHCP.
Use NetworkInterface.getNetworkInterfaces" to get list of all active interfaces.
Then for each interface use NetworkInterface.getName.For a wifi it will be wlanXX (Samsung and other), tiwlanXX (Motorola), ethXX (HTC) and e0 (Blackberry). With mobile net it most certainly be rmnetXX.
Then you can use NetworkInterface.getInetAddrress for creating socket.
When WiFi is unavailable, only rmnet appears. Similarly, when WiFi is available, rmnet will not appear, even if Mobile Data is enabled.
Added 30May17 In fact, I doubt that is possible to do what you intend, as it looks like the mobile network is automatically switched off when WiFi is available.
WifiManager wifiManager =
(WifiManager) context.getApplicationContext ()
.getSystemService (Context.WIFI_SERVICE);
.....
int localInetAddressInt = wifiManager.getDhcpInfo ().ipAddress;
ByteBuffer tmp = ByteBuffer.allocate (4);
tmp.putInt (localInetAddressInt);
byte swap; for(int i = 0; i < 2; i++) { swap = tmp.get(i); tmp.put(i, tmp.get(3 - i)); tmp.put(3 - i, swap); }
InetAddress localInetAddress = InetAddress.getByAddress (tmp.array ());
DatagramSocket socket = new DatagramSocket (portNumber, localInetAddress);
This worked for me!
EDIT:
Nope it doesn't. When Mobile data is ON, it doesn't go via WIFI

How to detect if a network is (configured as) a mobile hotspot on Android?

As of Android 4.1, your device can detect if it's connected to a mobile hotspot (given that the mobile hotspot is also running Android 4.1 or higher). Also, you have the option to flag networks as mobile hotspots (under Settings / Data Usage / Overflow menu / Mobile Hotspots).
But how do I detect this as a -user- I meant developer? It's not stored in the WifiConfiguration, so where is it?
Some context: I want to build a simple tool for Android that checks if you are connected to a network that you or Android has flagged as a mobile hotspot. If so, it will check if no other (non-hotspot) networks are available. If so, it should connect to these other networks since those should be much faster and have no data cap. Why? Because my phones and tablets connect to (mobile) hotspots quite often, even when a better network is available.
Here is some pseudo code of what I'm looking for:
// Check if android has detected mobile hotspot
WifiManager wifiMgr = getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiMgr .getConnectionInfo();
boolean isMobileHotspot = wifiInfo.isMobileHotspot;
UPDATE Jul 3rd 2014
Okay so Matiash' answer is good but ConnectivityManager.isActiveNetworkMetered() will only return the value for the current network. I do need that, so it helped me along, but it bring me to the next part in my tool/app:
IF the device is connected to a mobile hotspot (or a 'metered network' as Android calls it) I want to check if any of the nearby access points is a better option. So I need to know whether any of the known AP's (WifiManager.getConfiguredNetworks()) is also flagged as such before I connect to it...
I have a List<ScanResult> and a List<WifiConfiguration>, looks like neither of them has this information.
Which bring me back to my initial question: Is there a way to retrieve the Mobile Hotspots (as configured by Android and/or user) under Data Usage? And this time I mean ALL of them.
UPDATE Jul 7th 2014
I've posted a feature request in the AOSP Issue Tracker for access (readonly) to the NetworkPolicyManager. Plz vote on it here: https://code.google.com/p/android/issues/detail?id=73206&thanks=73206&ts=1404719243
You can access this information by calling ConnectivityManager.isActiveNetworkMetered().
This will return whether the active connection is a hotspot (as defined in Data Usage -> Mobile Hotspots).
About the second part, I'm sorry but I don't think that's possible. The flag is not public, and even if you get the object that could be used to retrieve it (android.net.NetworkPolicyManager) by reflection:
Object npm = Class.forName("android.net.NetworkPolicyManager").getDeclaredMethod("from", Context.class).invoke(null, this);
Object policies = npm.getClass().getDeclaredMethod("getNetworkPolicies").invoke(npm);
calling getNetworkPolicies() requires the MANAGE_NETWORK_POLICY permission, which cannot be obtained by non-system apps, because it has a "signature" protection level.
I hope to be proved incorrect though. :) Maybe looking at the source code of the Android activity that manages this information (https://github.com/android/platform_packages_apps_settings/blob/master/src/com/android/settings/net/DataUsageMeteredSettings.java), in particular the buildWifiPref() method, will provide some clue.
I do not know if what you want is possible but you can check whether your device is connected to a network by checking the ip.
You can use the tool below to see if you has ip, and shalt know if he is connected to a network or not.
public static Boolean check_connection(final Context _context)
{
boolean connected;
ConnectivityManager conectivtyManager = (ConnectivityManager) _context
.getSystemService(Context.CONNECTIVITY_SERVICE);
if (conectivtyManager.getActiveNetworkInfo() != null
&& conectivtyManager.getActiveNetworkInfo().isAvailable()
&& conectivtyManager.getActiveNetworkInfo().isConnected())
{
connected = true;
} else
{
connected = false;
}
return connected;
}
//Check if hotspot tethering is enabled
try {
ConnectivityManager connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
boolean isMobileData = connectivityManager.isActiveNetworkMetered();
if(isMobileData) {
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface networkInterface : interfaces) {
if (networkInterface.getName().equals("ap0")) {
//Tethering is enabled
SendHotspotEnabledHandler sendHotspotEnabledHandler = new SendHotspotEnabledHandler(new WeakReference<Context>(SendInstalledAppsService.this));
sendHotspotEnabledHandler.execute();
break;
}
}
}
} catch (SocketException e) {
}

Is there anyway in which I can set the SSID and passphrase of my choice in wifi direct group creation

I want to create a p2p connection between a normal android WiFi device and another android device with WiFi direct support.
Am successfully able to create a group(uisng createGroup of WifiP2pManager) and using the SSID and pass phrase given by the android I am also successfully able to connect a normal WiFi device to my WiFi-direct enabled device( in which I created group using wifi direct apis).
But here android gives some random WiFi SSID and pass phrase , which results in me looking at the adb logs always for SSID name and then entering in the other device.
Is there anyway in which I can set the SSID and passphrase of my choice?
Thanks
Kozlov
requestGroupInfo() enables you to get both the SSID and passphrase, however, I don't think it's possible to adjust these (yet)..
Firstly, the best way would be not to change but only retrieve the WifiP2p settings and pass them you a connecting legacy device (one that does not support WifiP2p because only there you need a passphrase) using a different channel like bluetooth or NFC. A QR code may also work.
The previous Message showed you, how to get you SSID and Passphrase. The Passphrase can't be changed, however, the SSID can. The Wifi Direct spec settles the SSID to be "DIRECT__" where xy are some random generated letters during setup. So you cant change this prefix "DIRECT" and the two letters, because both the letters and passphrase are generated in internal libraries and only a read only copy is passed back to the application.
However you can change what comes afterwards the SSID prefix using reflection API.
private void openWifiDirectChannel(String name){
WifiP2pManager manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
WifiP2pManager.Channel channel = manager.initialize(this, getMainLooper(), null);
//Use reflection API to get the hidden method for setting the P2P network name
try {
Method m = manager.getClass().getMethod(
"setDeviceName",
WifiP2pManager.Channel.class, String.class, WifiP2pManager.ActionListener.class );
m.invoke(manager, channel, name, new WifiP2pManager.ActionListener() {
public void onSuccess() {
//Code for Success in changing name
}
public void onFailure(int reason) {
//Code to be done while name change Fails
}
});
} catch (Exception e)
e.printStackTrace();
}

Categories

Resources