I'm implementing an application where I have to discover devices using WiFi p2p and record some data about every detected device. The problem I'm having is that when a device goes from online to offline, the WifiP2pDeviceList is not being updated so that it removes this device. But when a new device a detected it is added normally to the list. So what is the problem with my code?
I already saw this post ( WIFI P2P discovery list is not getting refreshed? ) and it is different from what I have.
/**
* A Table of devices that displays all peers and shows the corresponding
* info about them.
*/
public class DevicesTable extends Fragment implements PeerListListener {
protected static List<WifiP2pDevice> peers = new ArrayList<WifiP2pDevice>();
View mContentView = null;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mContentView = inflater.inflate(R.layout.table, null);
return mContentView;
}
/**
* #return this device
*/
public WifiP2pDevice getDevice() {
return device;
}
#Override
public void onPeersAvailable(WifiP2pDeviceList newPeers) {
ClientActivity x = (ClientActivity) ClientActivity.context;
if (!x.wifiP2pScanThread.isScanning()) {
return;
}
peers.clear();
peers.addAll(newPeers.getDeviceList());
ArrayList<String> onlinemacs = new ArrayList<>();
if (peers.size() == 0) {
Log.d(ClientActivity.TAG, "No devices found");
} else {
for (WifiP2pDevice dev : peers) {
//do something
}
}
}
}
I've tried many alternatives suggesting re-initializing the wifip2p manager and channel or calling peers.clear() but none of them worked.
Then I came across this (perfect) explanation to what's going on, it says that the list will be cleared after a specific period time (roughly a minute, it depends on the phone) and there is no feasible way of doing that using the available software. Note that manually turning off/on the Wifi does reset the list (and almost all other wifi-related settings), but that's not practical at all.
So in a nutshell there is no way to clear the previously-not-available-anymore peers programatically.
Related
I'm having issues trying to connect to multiple devices running the same app. The workflow of my app is:
one device calls discover peers (see code A)
once peers are discovered I display an AlertDialog that allows the user to select which peers they want to connect to (see code B)
once the user selects the devices they want to connect to I attempt to loop through the WifiP2pDeviceList and call the connect method on each of the passed in device (I always set the intent of the current device as the group owner) (see code C)
once connection is made I transfer some data....
Issue: In step 3, when I call the connect method, the code connects to the first device without any problems, but when it gets to the second loop iteration to connect to the second or third device that was selected I get a failure with reason code 2 (Busy). Why is this happening? When I only connect to one device it all works fine, only when I attempt to connect to more than one is when I have the problems? What am I doing wrong? I can't find any examples of how to connect to multiple devices...any help is greatly appreciated.
Code A: (discover peers)
private WifiP2pManager mManager;
mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
onInitiateDiscovery();
}
#Override
public void onFailure(int reasonCode) {
Toast.makeText(getActivity(), "Discovery Failed: " + getReascodeText(reasonCode), Toast.LENGTH_SHORT).show();
}
});
Code B: (select peers you want to connect to)
public void onPeersAvailable(WifiP2pDeviceList peers) {
final ArrayList<Integer> itemsSelected = new ArrayList<>();
// Out with the old, in with the new.
mPeers.clear();
mPeers.addAll(peers.getDeviceList());
CharSequence[] cs = StringUtils.getDeviceNames(mPeers);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Who do you want to share with?");
builder.setMultiChoiceItems(cs, null, new DialogInterface.OnMultiChoiceClickListener() {
public void onClick(DialogInterface dialog, int selectedItemId, boolean isChecked) {
if (isChecked) {
itemsSelected.add(selectedItemId);
} else if (itemsSelected.contains(selectedItemId)) {
itemsSelected.remove(Integer.valueOf(selectedItemId));
}
}
}).setPositiveButton("Done!", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int id) {
WifiP2pDevice wifiP2pDevice = mPeers.get(itemsSelected.get(0));
List<WifiP2pDevice> devices = extractSelectedDevices(itemsSelected,mPeers);
numConnections = devices.size();
connect(devices);
}
});
mPeerSelectionDialog = builder.create();
mPeerSelectionDialog.show();
}
Code C: (connect to selected devices)
public void connect(List<WifiP2pDevice> devices) {
for(WifiP2pDevice device: devices) {
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = device.deviceAddress;
config.wps.setup = WpsInfo.PBC;
config.groupOwnerIntent = 15;
mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
// WiFiDirectBroadcastReceiver will notify us. Ignore for now.
System.out.println("successfully connected!!");
Log.d(MultiImageSelectorFragment.TAG, ">>>>>>>>>>>>>>>>>>>>>!!Successfully Connected!<<<<<<<<<<<");
}
#Override
public void onFailure(int reason) {
Toast.makeText(getActivity(), "Connect failed. Retry.", Toast.LENGTH_SHORT).show();
Log.d(MultiImageSelectorFragment.TAG, ">>>>>>>>>>>>>>>>>>>>>!!Failed connection, rasoncode:"+reason+" !<<<<<<<<<<<");
}
});
}
That's correct, after the connection to the first device it will fail to connect with busy error code. The reason is that the WiFiP2P at your device didn't finish yet the first device connection process (even though you've accepted the connection and it got established, it still takes some time to create the group and release the resources).
To overcome this issue, you can start another thread with some delay (from my tests at least 10-15 seconds) to attempt to connect to the second device.
Goodluck.
I connect bluetooth barcode scanner to my android tablet. barcode scanner is bonded with android device as a input device - HID profile. it shows as keyboard or mouse in system bluetooth manager. i discovered that bluetooth profile input device class exist but is hidden. class and btprofile constants have #hide annotaions in android docs.
hidden class:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.3.1_r1/android/bluetooth/BluetoothInputDevice.java
here they should be also 3 other constants
developer.android.com/reference/android/bluetooth/BluetoothProfile.html#HEADSET
just like
public static final int INPUT_DEVICE = 4;
public static final int PAN = 5;
public static final int PBAP = 6;
that constants are simple accessible by reflection.
What i need to achieve, is list of devices by hid profile(INPUT_DEVICE). it should be simple with small changes using method:
developer.android.com/reference/android/bluetooth/BluetoothA2dp.html#getConnectedDevices()
not for A2dp profile, but for hid profile accessed also by reflection methods.
sadly
Class c = Class.forName("android.bluetooth.BluetoothInputDevice")
won't work..
any ideas how i should approach to the problem ? i need only list of hid devices
I figured out how to solve my problem.
That was very helpful.
First of all I needed to prepare reflection method which return input_device hidden constants of hid profile:
public static int getInputDeviceHiddenConstant() {
Class<BluetoothProfile> clazz = BluetoothProfile.class;
for (Field f : clazz.getFields()) {
int mod = f.getModifiers();
if (Modifier.isStatic(mod) && Modifier.isPublic(mod) && Modifier.isFinal(mod)) {
try {
if (f.getName().equals("INPUT_DEVICE")) {
return f.getInt(null);
}
} catch (Exception e) {
Log.e(LOG_TAG, e.toString(), e);
}
}
}
return -1;
}
Instead of that function, I could use value 4, but i want to do it elegant.
Second step was to define listener of specific profile:
BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
#Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
Log.i("btclass", profile + "");
if (profile == ConnectToLastBluetoothBarcodeDeviceTask.getInputDeviceHiddenConstans()) {
List<BluetoothDevice> connectedDevices = proxy.getConnectedDevices();
if (connectedDevices.size() == 0) {
} else if (connectedDevices.size() == 1) {
BluetoothDevice bluetoothDevice = connectedDevices.get(0);
...
} else {
Log.i("btclass", "too many input devices");
}
}
}
#Override
public void onServiceDisconnected(int profile) {
}
};
In third step I invoked
mBluetoothAdapter.getProfileProxy(getActivity(), mProfileListener,
ConnectToLastBluetoothBarcodeDeviceTask.getInputDeviceHiddenConstant());
Everything clearly works and mProfileListener returns me list of specific profile bluetooth device/-es.
Most interesting thing takes place in onServiceConnected() method, which returs object of hidden class BluetoothInputDevice :)
As in the title I've been able to connect to Google Game Services, exchange data between two devices and everything is running fine, except one thing: disconnection callbacks.
I tried to intercept both onPeersDisconnected and onP2PDisconnected without any success. The onP2PDisconnected method is being called in the device that get disconnected from Internet but not into device that is still online (so there is no way to tell the player that the other one got disconnected).
After the match is started it seems that the second device is never notified of the accidental disconnection. If the user close the game properly the onPeersLeft method is being called thought.
Is a ping between the two devices really necessary to overcome this "bug"? Am I doing something wrong?
Here is the code I use:
void startQuickGame() {
// quick-start a game with 1 randomly selected opponent
final int MIN_OPPONENTS = 1, MAX_OPPONENTS = 1;
Bundle autoMatchCriteria = RoomConfig.createAutoMatchCriteria(MIN_OPPONENTS,
MAX_OPPONENTS, 0);
RoomConfig.Builder rtmConfigBuilder = RoomConfig.builder(this);
rtmConfigBuilder.setMessageReceivedListener(this);
rtmConfigBuilder.setRoomStatusUpdateListener(this);
rtmConfigBuilder.setAutoMatchCriteria(autoMatchCriteria);
mListener.switchToScreen(R.id.screen_wait);
keepScreenOn();
resetGameVars();
getGamesClient().createRoom(rtmConfigBuilder.build());
}
And here the simple listeners:
#Override
public void onPeersDisconnected(Room room, List<String> peers) {
Log.d(TAG, "onPeersDisconnected");
updateRoom(room);
}
void updateRoom(Room room) {
Log.d(TAG, "UpdateRoom: "+room.getParticipants().size());
mParticipants = room.getParticipants();
}
#Override
public void onP2PDisconnected(String participantId) {
Log.d(TAG, "onP2PDisconnected");
}
public int getPartecipantsInRooom(){
if(mRoom != null)
return mRoom.getParticipants().size();
else
return -123456;
}
Note that calling getPartecipantsInRooom() after one of the two devices disconnects always return 2, and updateRoom never get called.
Just to be sure this might not work for you, for my applications I use this to let me know when another Participant has left the Room, and it is called immediately :
#Override
public void onPeerLeft(Room room, final List<String> participantIds) {
this.mRoomCurrent = room;
this.mRoomId = this.mRoomCurrent.getRoomId();
this.mParticipants = this.mRoomCurrent.getParticipants();
int connected = 0;
for (Participant p : room.getParticipants()) {
if(p.getStatus() == Participant.STATUS_JOINED) {
connected += 1;
}
}
final int fconnected = connected;
for (String s : listIgnoreTheseIDs) {
//checkint to see if we care anymore about this ID.. if out of game already.. nope
if(s.equals(participantIds.get(0))){
return;
}
}
Gdx.app.postRunnable(new Runnable() {
#Override
public void run() {
mGHInterface.onPeerLeft(fconnected, participantIds.size());
}
});
}
No idea why there are two items, but like you, I realized the onPeersDisconnected() isn't that reliable, but onPeerLeft() normally gets back to the other devices in under 1 second.
onPeerDisconnected() handles disconnects. So if somebody is still in the application but the network connection is lost, this is called for him.
onPeerLeft() handles participants who leave a room. This is called when somebody explizit leaves the room in the application or the application is minimized, and the room is left on the androids onStop() or onDestroy() callback.
I'm making two player game. So I use this approach
#Override
public void onPeerLeft(Room room, List<String> peersWhoLeft) {
updateRoom(room);
Toast.makeText(MyLauncherActivity.this, "Other player left the game", Toast.LENGTH_LONG).show();
quitGame();
}
I'm pretty new with Android programming. But I have been working on this for over a week now, and it starts to get booooring.
My idea is that I want to connect two devices using Wifi Direct. But I only want to connect to those which are running my application. Besides, I want the users to be able to see some information of the other devices (such as user name), not just the MAC or the Android_XXXX name included in the WifiP2pDevice. That's why I decided that a device looking for other devices, should both start the application service and search for peers which are also broadcasting this service.
The problem (I'm testing with two real devices) is that, even though they are running exactly the same code, only one of them is getting the service discovery callbacks (the onDnsSd... listeners below). So, one side acts in the proper way, but not the other. Moreover I'm getting "old" services, meaning that apparently each time I start de service (even though I cancel previously started services), that service seems to be still broadcast during at least some minutes.
I include a shortened version of my code:
public class MoveFlufietsDialogFragment extends DialogFragment implements ChannelListener, DeviceActionListener {
public final HashMap<String, FlufietsPeer> mBuddies = new HashMap<String, FlufietsPeer>();
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
...
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);
mManager = (WifiP2pManager) getActivity().getSystemService(Context.WIFI_P2P_SERVICE);
mChannel = mManager.initialize(getActivity(), getActivity().getMainLooper(), null);
...
startRegistration();
discoverFlufietsService();
...
}
public void discoverFlufietsService() {
DnsSdTxtRecordListener txtListener = new DnsSdTxtRecordListener() {
#Override
public void onDnsSdTxtRecordAvailable(String fullDomain, Map record, WifiP2pDevice device) {
// This and the next listener are only called in one of the devices.
String serviceName = (String) record.get("serviceName");
if ((serviceName != null) && (serviceName.equals("flufiets")) {
// I put the record data in the mBuddies HashMap.
...
mBuddies.put(device.deviceAddress, myPeerDataStructure);
}
}
};
DnsSdServiceResponseListener servListener = new DnsSdServiceResponseListener() {
#Override
public void onDnsSdServiceAvailable(String instanceName, String registrationType, WifiP2pDevice resourceType) {
if (mBuddies.containsKey(resourceType.deviceAddress)) {
FlufietsPeer flufietsPeer = mBuddies.get(resourceType.deviceAddress);
WiFiPeerListAdapter adapter = ((WiFiPeerListAdapter) mFragmentList.getListAdapter());
adapter.add(flufietsPeer);
adapter.notifyDataSetChanged();
}
}
};
mManager.setDnsSdResponseListeners(mChannel, servListener, txtListener);
WifiP2pDnsSdServiceRequest serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
mManager.addServiceRequest(mChannel, serviceRequest, new ActionListener() {
// onSuccess/onFailure toasts.
});
mManager.discoverServices(mChannel, new WifiP2pManager.ActionListener() {
// onSuccess/onFailure toasts.
});
}
public void startRegistration() {
mManager.clearLocalServices(mChannel, new ActionListener() {
// onSuccess/onFailure toasts.
});
Map record = new HashMap();
record.put("serviceName", "flufiets");
...
WifiP2pDnsSdServiceInfo serviceInfo = WifiP2pDnsSdServiceInfo.newInstance(flufietsService, "_tcp", record);
mManager.addLocalService(mChannel, serviceInfo, new ActionListener() {
// onSuccess/onFailure toasts.
});
}
#Override
public void onResume() {
super.onResume();
mReceiver = new WiFiDirectBroadcastReceiver(mManager, mChannel, this);
getActivity().registerReceiver(mReceiver, mIntentFilter);
}
#Override
public void onPause() {
super.onPause();
getActivity().unregisterReceiver(mReceiver);
}
#Override
public void onStop() {
super.onStop();
mManager.clearLocalServices(mChannel, new ActionListener() {
// onSuccess/onFailure toasts.
});
}
...
}
The problem doesn't seem to be related with the device itself (sometimes it works, sometimes it doesn't, but always only in one of them). I suspect it has to do with either trying to discover a service that we ourselves are broadcasting, or having the same service being offered by two devices. I have tried changing the names of the service, so each device would offer either a "send" or "receive" service, but it doesn't work. I only get the callbacks called (onDnsSd...) in one of the devices.
And that thing about getting old services, when I always clear them, is weird (I do include a timestamp in the service record data, and I could always discard all but the last, but doesn't seem to be logical).
Any ideas? ANY help would be VERY appreciated, because writing the application is not funny any more (:-)=
Thanks a lot!
You need to wait until the clearLocalService call succeeds before adding the local service later. So put the addLocalService call into the onSuccess callback of the clearLocalServices.
I know that Wifi Direct works by creating a Soft AP (software access point) in one of the devices. I also know that many Androids support Wifi Direct, but iPhones do not.
My question is: is it possible to create a device-to-device wifi link that is Wifi Direct on the Android side, but regular wifi on the iPhone side? Where the Android's Wifi Direct would be presenting a soft AP, which the iPhone would see as indistinguishable from a regular AP and be able to associate to.
Imagine that this is out in the wilderness where no router AP is available. Also, neither user has a tethering plan.
This link would be used by a Bump-like app to transfer files.
Depending on your phone you can just set up your Android phone as a portable hotspot and connect to that with the iPhone. From there it would be application specific to get data transferred.
However you can also use the Androids WiFi-Direct libraries. In that case you would use them to set up the Android phone to create a "Group owner", which basically is the same as it being a portable hotspot. Check out:
http://developer.android.com/guide/topics/connectivity/wifip2p.html
I'll give you a code example to help you get started.
public class WifiDirectAPtestActivity extends Activity
{
private WifiP2pManager manager;
private boolean isWifiP2pEnabled = false;
private boolean retryChannel = false;
private final IntentFilter intentFilter = new IntentFilter();
private Channel channel;
private BroadcastReceiver receiver = null;
public void setIsWifiP2pEnabled(boolean isWifiP2pEnabled) {
this.isWifiP2pEnabled = isWifiP2pEnabled;
}
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// add necessary intent values to be matched.
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);
manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
channel = manager.initialize(this, getMainLooper(), null);
}
/** register the BroadcastReceiver with the intent values to be matched */
#Override
public void onResume() {
super.onResume();
receiver = new WiFiDirectBroadcastReceiver(manager, channel, this);
registerReceiver(receiver, intentFilter);
createGroup();
}
#Override
public void onPause() {
super.onPause();
unregisterReceiver(receiver);
manager.removeGroup(channel, new ActionListener() {
#Override
public void onFailure(int reasonCode) {
Log.d("WifiDirectAptestActivity", "Disconnect failed. Reason :" + reasonCode);
}
#Override
public void onSuccess() {
Log.d("WifiDirectAptestActivity", "Should have been sucessfully removed");
}
});
}
public void createGroup()
{
manager.createGroup(channel, new ActionListener() {
#Override
public void onSuccess() {
// WiFiDirectBroadcastReceiver will notify us. Ignore for now.
Log.d("WifiDirectAPtestActivity", "Group creating request successfully send");
}
#Override
public void onFailure(int reason) {
Toast.makeText(WifiDirectAPtestActivity.this, "Connect failed. Retry.",
Toast.LENGTH_SHORT).show();
}
});
}
In addition you'll need the broadcast receiver, look at the WiFi-Direct demo and it should be clear to you.
Note that line manager.createGroup(channel, new ActionListener() is the codeline of interest, it is this line that actually sets up the device as a portable hotspot.
Hope this clarifies things, I don't really know how detailed explanation you need. Comment if some things are not clear.