WifiP2pDevice status stays on INVITED after Wifi Direct Invitation - android

I am programming a Wifi Direct game, but have run into a problem with the WifiP2pDevice Status when one user declines the invitation to join the connection.
Phone A initiates connection to Phone B
Phone B status becomes INVITED (as displayed on Phone A)
Phone B decline invitation from Phone A
Phone B status is still INVITED (as displayed on Phone A)
Shouldn't the status be shifted back to AVAILABLE as displayed on Phone A?
I have refreshed the list of Available devices, but the status remains unchanged. even if I restart the app, it still shows the status of Phone B as invited?
Is this supposed to happen based on the API of WifiDirect? or am I missing something?
Edit: More Information
In the BroadcastReciever, when the intent is WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION, then the manager requests the peers using a Fragment that implements a PeerListListener, which just prints a list of available devices and information (including status) of those devices. So when Phone A sends an invitation with manager.connect() it changes the status to INVITED. But if Phone B declines, the status remains as INVITED. And those statuses are triggered on a notifyDataSetChanged(). It is not so much a question about code, but how wifi direct determines and changes statuses of the devices. But I can provide code if needed.

You can cancel the connection if the status is "Invited"
if (fragment.getDevice().status == WifiP2pDevice.AVAILABLE
|| fragment.getDevice().status == WifiP2pDevice.INVITED) {
manager.cancelConnect(channel, new ActionListener() {
#Override
public void onSuccess() {
Toast.makeText(WiFiDirectActivity.this, "Aborting connection",
Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(int reasonCode) {
Toast.makeText(WiFiDirectActivity.this,
"Connect abort request failed. Reason Code: " + reasonCode,
Toast.LENGTH_SHORT).show();
}
});
}

Related

Android - Wifimanager handle wifi-connection states

I've got an app which connect itself programatically to a wifi connection. My problem is, I want to handle the case, that the password is wrong. I want to detect that the password is not correct in runtime. To be precise I've got a progressdialog running while the connection is established, so if the password is wrong the progressdialog is just shown all the time and can't be skipped. A further note: I handled a password which is less than 8 characters by using this code:
if(!m_wifiManager.enableNetwork(netId, true)) {
progressDialogConnecting.dismiss();
createInfoMessageDialog(CONST.WIFI_CON_FAILED_TITLE, CONST.WIFI_CON_FAILED_MSG_CONFAILURE);
m_wifiManager.reconnect();
return;
}
If the key for the wifi connection is less than 8 characters, this if-case gets triggered. But if it is longer than 8 characters and wrong I get an endless state of showing the progress dialog.
What I exactly want to ask: how do I handle 1. wrong password 2. connection states (just like Android system showing me the toasts "Connected to Wifi xyz") ? AND is it even possible to handel the first one (wrong password)?
Here is the code, that did not work for handling connection established event (this is just the wifirecevier, I also registered it in the activity):
public class WifiReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) {
if (intent.getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false)){
if(wrongNetworkConnected)
progressDialogConnecting.dismiss();
}
}
} else {
}
}
}
}
Edit: What I am currently doing, is that I have a Handler which tells me to whom I am connected. That's useful because I can say that after the reconnect() I am reconnected to the old network (current network) and not the new one - so apparently the password could be wrong (or something else), because I could not connect to the new network.
The problem about this method is that first of all it takes too much time and secondly it is not reliable. I can lie and say that if you will get reconnected to your current network it is the fault of a wrong password, but actually it is not 100% sure that you cannot reconnect because of this - it may also have other reasons. So I am still searching for a simple feedback/handle from the suplicant that the password is wrong, just like the android api does in the wifi settings of each android device...
My problem is, I want to handle the case, that the password is wrong.
After some research I found this post which is not marked as answered but it still worked for me very well.
Here is the if-case in which the program jumps (already tested several times by me) if there is an authentication error --> e.g. wrong password:
int supl_error=intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1);
if(supl_error==WifiManager.ERROR_AUTHENTICATING){
// DO SOMETHING
}
NOTE: As seen in the linked post above this if-case should appear in a BroadcastReceiver adding the intent WifiManager.SUPPLICANT_STATE_CHANGED_ACTIONto the receiver-registration in your activity-class.

Get notified when saved Wi-Fi network configuration is removed

The scenario is the following:
User has connected to a several Wifi profiles, so one can go to Wifi Settings, Saved Networks and see the list of previously connected and saved network configurations.
User has ability to remove a network from the list.
I need to perform some action upon removal of the network from the list. Ho do I intercept the removal action?
Thanks!
I could not find any information on profile management, the only piece of info is that when profile is changed (added/modified/removed) connection state change event occurs. Based on that info I got it working by doing the following:
if (action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) {
SupplicantState ss = intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE);
if (ss == SupplicantState.DISCONNECTED || ss == SupplicantState.COMPLETED) {
// wifi profile got removed
}
} else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)
|| action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
// bluetooth profile got removed
}
Note, no intent is issued when inactive profile got removed.

Bluetooth pairing - how to show the simple Cancel/Pair dialog?

I have prepared a simple test project for this question at GitHub.
I am trying to create an Android app, which would scan a QR code from a computer screen and then use the data (MAC address and PIN or hash) for easy pairing (bonding) with a Bluetooth device.
Similar to the popular InstaWifi app - but for Classic Bluetooth.
For testing purposes I don't do any barcode scanning yet, but just display a list of devices:
After user touches one of the devices, pairing is tried in MainActivity.java:
private void startBluetoothPairing(BluetoothDevice device) {
Intent pairingIntent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
pairingIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
pairingIntent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
BluetoothDevice.PAIRING_VARIANT_PIN);
pairingIntent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, 1234);
//device.setPin(new byte[]{1,2,3,4}); <- DOES NOT CHANGE ANYTHING
//device.setPairingConfirmation(false);
startActivityForResult(pairingIntent, REQUEST_BT_SETTINGS);
}
Unfortunately, the popup still asks for PIN:
Because I have actually specified a PIN in my source code, I was actually expecting another, simpler system dialog to be shown (this one is shown when doing NFC OOB pairing):
From searching for solutions, I know that there is a setPin() method, but it is not applicable here (or is it?) - because I am trying to pair the whole smartphone to the Bluetooth device and not just the app...
My question: How to make Android OS to show the simple Cancel/Pair dialog?
Searching for Bluetooth pairing request string at GitHub has not shown any hints...
UPDATE: On unrealsoul007's suggestion (thanks) I have update the source code in MainActivity.java and now the simple Cancel/Pair dialog is displayed:
private void startBluetoothPairing(BluetoothDevice device) {
Intent pairingIntent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
pairingIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
pairingIntent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION);
pairingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(pairingIntent, REQUEST_BT_PAIRING);
}
However I am not sure how to complete the pairing process - because onActivityResult is called with resultCode=0 even before the dialog is closed:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
// this is called before user clicks Cancel or Pair in the dialog
if (requestCode == REQUEST_BT_PAIRING) {
if (resultCode == Activity.RESULT_OK) { // 0 != -1
Log.d("XXX", "Let#s pair!!!!"); // NOT CALLED
}
return;
}
}
You are being prompted for entering the pin because that is what you are requesting in your pairingIntent.
Instead of using
pairingIntent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
BluetoothDevice.PAIRING_VARIANT_PIN);
pairingIntent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, 1234);
Use
pairingIntent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, PAIRING_VARIANT_PASSKEY_CONFIRMATION);
As mentioned here,
The user will be prompted to confirm the passkey displayed on the
screen or an app will confirm the passkey for the user.

Why is discovering peers for Android Wi-Fi Direct so unreliable?

I am experimenting with Android's Wi-Fi Direct (or P2P if you prefer). I thought it was working very nicely between my two phones, but I realized I am encountering issues with the WifiP2pManager.discoverPeers() and/or WifiP2pManager.requestPeers(). I have observed these results:
No peers are discovered and no callbacks are fired for a good 1+ minute. I observe this through the Wi-Fi Direct portion of the Android Wi-Fi settings as well. This is odd because sometimes the discovery completes almost immediately.
I have a Roku device and phone B sitting next to phone A. While all are connected to my Wi-Fi, the Roku only appears ~10% of the time, while phones A and B appear to each other.
When I disconnected the two phones from all Wi-Fi and did another scan, the Roku showed up (!!!) but phone B did not until I had refreshed at least ten times.
My two phones are a Nexus 7 (running 4.4.4) and a Nexus 5 (running 5.0).
I've been recently developing an application with a connection system based on WiFi Direct (with WiFi P2P Service Discovery) and the one thing I can most certainly say is that the whole thing is a huge pain in the ... . Mostly because of the lack of documentation but also because when developing a wifi-direct-based solution you need to pay attention to basically everything (especially to all callbacks from listeners) before making any method call.
Two most annoying things were I guess:
An undocumented UNKNOWN_ERROR (I think its int code was -3) that is being thrown in ActionListener onFailure method. It seems to be some sort of issue with the wifi daemon itself. The only thing that seems to work to prevent it from happening is resetting WiFi before you even start messing around with WiFi direct.
Something being in the wrong state for your method call - for example if WIFI_P2P_STATE_CHANGED_ACTION has not been received in your broadcast receiver with the WIFI_P2P_STATE_ENABLED or if 'your_device' has not received a proper status in the WIFI_P2P_THIS_DEVICE_CHANGED_ACTION. This usually results in onFailure call in one of your ActionListeners (with for example ERROR or BUSY failure reason).
From my experience it's reliable. After tons of trying, I got the robust workable flow like this:
...
wifiP2pManager.clearLocalServices(wifiP2pChannel, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
HashMap<String, String> record = new HashMap<>();
record.put("name", "Amos");
WifiP2pDnsSdServiceInfo serviceInfo = WifiP2pDnsSdServiceInfo.newInstance(AppConfig.DNS_SD_SERVICE_NAME, AppConfig.DNS_SD_SERVICE_TYPE, record);
wifiP2pManager.addLocalService(wifiP2pChannel, serviceInfo, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
wifiP2pManager.setDnsSdResponseListeners(wifiP2pChannel, WifiDirectFragment.this, WifiDirectFragment.this);
wifiP2pManager.clearServiceRequests(wifiP2pChannel, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
wifiP2pManager.addServiceRequest(wifiP2pChannel, WifiP2pDnsSdServiceRequest.newInstance(), new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
wifiP2pManager.discoverPeers(wifiP2pChannel, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
wifiP2pManager.discoverServices(wifiP2pChannel, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
// this is my recursive discovery approach
handler.postDelayed(discoveryRunnable, AppConfig.DNS_SD_SERVICE_DISCOVERABLE_DURATION_S * 1000);
}
#Override
public void onFailure(int code) {
}
});
}
#Override
public void onFailure(int code) {
}
});
}
#Override
public void onFailure(int code) {
}
});
}
#Override
public void onFailure(int code) {
}
});
}
#Override
public void onFailure(int code) {
}
});
}
#Override
public void onFailure(int code) {
}
});
I was able to "solve" the problems of some phones not appearing by requesting peer discovery every 10 seconds. I think I was running into this because one phone was always the host and I didn't bother to have it discover peers (because it doesn't try to join them), and the Wifi Direct was going to sleep on the host phone. I don't do anything with the peer results, but it wakes up the Wifi Direct system. There's probably a better method to call but I'm not sure what it is. If I had to guess I'd say I'm wasting some battery life.
I had a really big problem with establishing connection between devices:
first device turns on peer discovery
second device turns on peer discovery
one device tries to establish connection with the second one
sometimes it works, sometimes not (I would say 50/50)
I guess the issue was the group owner negotiation (I've tried change groupOwnerIntent param also to force who should be group owner, but it didn't helped).
So, what did I do?
I change flow to:
one device creates group (mManager.createGroup(...)), so this device is always a group owner
second devices connects with group owner
whooala, now is very rare to stuck on invited state.

How do I keep a ChromeCast route alive when my app is in the background on battery power?

I'm working on an Android app that supports sending music to a ChromeCast. We'd like users to be able to cast entire music playlists while the app runs in the background.
When my Nexus 7 is not connected to USB power and I turn the screen inactivity timeout to 15 seconds in the settings, the app will disconnect from the ChromeCast about 90 seconds after the device powers off its screen.
I've identified that I'm getting a MediaRouter.Callback call to onRouteUnselected, and since that's the callback I get when a user disconnects from a route, I'm handling it by tearing down the ApplicationSession.
When I plug back in and check the logcat, I see this message around the same time:
I/MediaRouter(19970): Choosing a new selected route because the current one is no longer selectable: MediaRouter.RouteInfo{ uniqueId=... }
Can I do anything to avoid the route being unselected when the app is in the background, or is there something else I can do to get the behavior I want?
I eventually got around this by refusing to disconnect the message streams and tear down the session when the route was disconnected under these conditions, and silently re-select the route when it became available again. The route gets deselected, but it does not affect my casting session.
To do this, I check to see if the route exists when it's unselected.
public void onRouteUnselected(final MediaRouter router, final RouteInfo route) {
if (!onUiThread()) {
new Handler(Looper.getMainLooper()).post((new Runnable() {
#Override
public void run() {
onRouteUnselected(router, route);
}
}));
return;
}
boolean isThisRouteAvailable = doesRouterContainRoute(router, route);
mRouteToReconnectTo = null;
if (isThisRouteAvailable) {
// Perform code to close the message streams and tear down the session.
} else {
// The route was unselected because it's no longer available from the router,
// so try to just keep playing until the message streams get disconnected.
mRouteToReconnectTo = route;
// Short-circuited a disconnect.
}
}
Later, when the route comes back, we can immediately re-select it.
#Override
public void onRouteAdded(MediaRouter router, RouteInfo route) {
super.onRouteAdded(router, route);
// if mRouteToReconnectTo is not null, check to see if this route
// matches it, and reconnect if it does with router.selectRoute(route)
}
#Override
public void onRouteSelected(final MediaRouter router, final RouteInfo route) {
if (!onUiThread()) {
new Handler(Looper.getMainLooper()).post((new Runnable() {
#Override
public void run() {
onRouteSelected(router, route);
}
}));
return;
}
if (areRoutesEqual(mRouteToReconnectTo, route)) {
// Short-circuited a reconnect.
mRouteToReconnectTo = null;
return;
}
mRouteToReconnectTo = null;
// Standard post-selection stuff goes here
}
There's no good way to compare two RouteInfo's, so I ended up writing a helper function that compared their description strings.
Rooster's answer is perfectly feasible and actually provides good insight as to how to re-connect to a route once it comes back online....
but....just to give further insight on what's going on....
You're getting...
I/MediaRouter(19970): Choosing a new selected route because the current one is no longer selectable: MediaRouter.RouteInfo{ uniqueId=... }
because when the device goes to sleep and is NOT plugged into a power source, the WIFI hardware is going into a low-power profile mode (and possibly shutting down entirely). This results in packet loss and subsequently causes the MedaRouter to fire the onRouteUnselected callback.
To prevent the Wifi from turning off you could set a WakeLock on the Wifi in the following manner:
WifiLock wifiLock;
WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF , "MyWifiLock");
wifiLock.acquire();
Using the flag WifiManager.WIFI_MODE_FULL_HIGH_PERF will keep the WIFI hardware alive and active when the device goes to sleep. Caution, this flag is only available to API 12 and above.
I tried using the WifiManager.WIFI_MODE_FULL flag when creating the WifiLock, but that didn't seem to do the trick.
Obviously anyone using any type of WifiLock or WakeLock should take considerable care in making sure locks released when no longer needed. Also, beware this will cause battery drain when the device screen is off.
If you used the sample code (Android in this case), you're probably doing this...
mSession.setStopApplicationWhenEnding(true);
mSession.endSession();
...when the route is unselected. If you instead do this...
mSession.setStopApplicationWhenEnding(false);
mSession.endSession();
...then you can clean up the session, but the Chromecast will keep the application alive. When the route becomes available again (or possibly when the user picks the device again) you can build a new session. I have yet to explore how to determine if the new session is talking to a "brand new" instance of the application or to the application left running from another session, but I'll update this answer when I do.

Categories

Resources