I have spent the last couple of days trying to make an app that keeps my Samsung Galaxy S3 mini (Android 2.1.4) discoverable for an "infinite" amount of time. My code looks currently as follows:
package com.example.downtoone;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.widget.Toast;
import com.example.downtoone.*;
import android.bluetooth.*;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
public class MainActivity extends Activity {
private BluetoothAdapter mBluetoothAdapter = null;
// Intent request codes
private static final int REQUEST_CONNECT_DEVICE = 1;
private static final int REQUEST_ENABLE_BT = 2;
private static final int REQUEST_ENABLE_DSC = 3;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
finish();
return;
}
}
#Override
public void onStart() {
super.onStart();
if (!mBluetoothAdapter.isEnabled()) {
Intent MDisc = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
MDisc.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,0);
startActivityForResult(MDisc, REQUEST_ENABLE_DSC);
}
}
#Override
public void onRestart(){
super.onRestart();
}
#Override
public void onResume() {
super.onResume();
}
#Override
public void onStop() {
super.onStop();
}
#Override
public void onDestroy() {
super.onDestroy();
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CONNECT_DEVICE:
if (resultCode == Activity.RESULT_OK) {
}
break;
case REQUEST_ENABLE_BT:
if (resultCode == Activity.RESULT_CANCELED) {
Toast.makeText(this, "BLUETOOTH NEEDS TO BE ENABLED AND DISCOVERABLE", Toast.LENGTH_SHORT).show();
finish();
}
break;
case REQUEST_ENABLE_DSC:
if (resultCode == Activity.RESULT_CANCELED) {
Toast.makeText(this, "BLUETOOTH NEEDS TO BE ENABLED AND DISCOVERABLE", Toast.LENGTH_SHORT).show();
//finish();
}
break;
}
}
public void finishBTsetup(){
}
}
Despite the fact that I am setting the time to '0', discoverability only runs for 2minutes. This is rather frustrating since I know the device can handle to be discoverable for an indefinite amount of time! ( I could manually access the bluetooth settings and set Bluetooth Visibility to 'Never Time Out'!)
I've looked all over for an answer without success... many posts give what (for a relative unskilled programmer such as me) look like arcane solutions that are either too vague(*), confusing(**) or downright wrong. A simple straightforward answer solving this issue (if it exists of course!) would be greatly appreciated!
(*)
Make Bluetooth on Android 2.1 discoverable indefinitely
(**)
Extend Android Bluetooth Discoverability
Android Application Bluetooth visibility duration (answer section)
MANIFEST:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.downtoone"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="14" />
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
EDIT:
To give people a little context, one of the goals of this application is to try and be DISCOVERABLE to all nearby Bluetooth devices so that it can directly talk to them. Since most smartphones are discoverable for short amounts of time (2min usually*) and only so when the user directly enables discoverability (= visibility), apps that scan for devices and automatically exchange data are impossible to implement. (* The user can usually set the visibility to 'No Time Out', but that requires the user to set that option directly under Bluetooth Settings of their smartphone, which is not a very elegant solution...)
I come to the same conclusion on three devices I have.
ANDROID v 4.3 and higher : EXTRA_DISCOVERABLE_DURATION 0 works no limit
ANDROIND v 4.1 : EXTRA_DISCOVERABLE_DURATION 0 is max 1 hour. Have to change manually to no limit in parameters.
above api level 14 EXTRA_DISCOVERABLE_DURATION 0 works with infinite limit but below this it works for max 1 hour.
This code works for me
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0);
startActivityForResult(intent, Utils.REQUEST_DEVICE_DISCOVERABLE);
According to Android documentation, the maximum time for being discoverable is capped at 300seconds. You cannot make the BT discoverable forever.
Please see:
http://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#ACTION_REQUEST_DISCOVERABLE
In order to get the 300second maximum period, you need to change one line of code as:
MDisc.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,300)
Im not sure if this would suit your application or if theres some other reason why you don't want to do this but why not just discover for the default time then restart discovery when it times out? this way it will technically be an unlimited discovery, which will only trigger a VERY slight pause in between, I used this method in my application using a broadcast receiver.
I put startDiscovery() in the onStart() method to trigger discovery on activity start, then listen to ACTION_DISCOVERY_FINISHED in your broadcast receiver's onReceive() method, here place another call to startDiscovery().
this will loop the discovery forever, if you want to stop when you find a device then call cancelDiscovery() in you receiver's ACTION_FOUND listener, you can also place some checking here if you need to find a particular device - again in my case i was checking for a particular device's mac address so the discovery would continue until this was found.
not sure if this is any use but if you need more detail let me know.
I've arrived to the conclusion that it can't be done, unless the user goes to Bluetooth > Settings > Visibility timeout and sets the timeout accordingly.
Peace.
Works to me.
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
enableBtIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0);
startActivityForResult(enableBtIntent, Utils.REQUEST_DEVICE_DISCOVERABLE);
Disable manually your Bluetooth and then run the code. It will works, yes.
Related
In Android 10 I noticed I get a Toast message from the OS stating "No supported application for this NFC tag" or "No supported app for this NFC tag" (depending on the device):
The weird thing is that I see the Toast while enableReaderMode is active in the foreground Activity. In all previous versions of Android, enableReaderMode would override the Android intent tag dispatch system. Is this a bug in Android 10?
I know enableForegroundDispatch also exists, and that API does seem to override the intent tag dispatch system even in Android 10. But I'd like to keep control over the NFC discovery sound which is only provided by enableReaderMode.
I also know that I can declare an intent-filter in my manifest to get rid of the Toast while continuing to use enableReaderMode, but that also has unintended side effects (e.g. my app could be launched while reading the NFC tag from the device home screen which I don't want).
Yes, it seems to be a bug as Android OS fires a new tagDiscovery event under the following conditions:
Use enableReaderMode instead of enableForegroundDispatch
Read or write a card
While the card is still in proximity call disableReaderMode.
Since this triggers an OS level event, it can pause focused activity, might show a toast screen or show related application select box.
To workaround the problem,
Workaround 1:
Try connecting the card in a loop until an IOException is fired. (which means card is not in proximity anymore)
Then call disableReaderMode
Cons: You may need to show a message to the user to remove the tag/card from the device proximity.
Workaround 2:
Use legacy enableForegroundDispatch / disableForegroundDispatch along with readerMode
Cons: Popup do not gets displayed, however tag discovery sound still gets triggered.
Both solutions do not need intent-filter to be defined.
Sample code that implements both workarounds is below.
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.Bundle;
import android.widget.Toast;
import java.io.IOException;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity
{
private NfcAdapter nfcAdapter;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//Assuming nfc adapter is present and active, such checks are ignored for code clarity,
//production code must check for hardware nfc adapter existence
nfcAdapter = NfcAdapter.getDefaultAdapter(this); //Get the default NFC adapter from OS
//Additional initialization code
}
private void onTagDiscovered(Tag tag)
{
try
{
if (tag == null)
return;
//Assumption: We're using an NFC card that supports IsoDep
IsoDep iso = IsoDep.get(tag);
if (iso == null)
return;
iso.setTimeout(1000);
iso.connect();
//doCardReadWrite(iso);
iso.close();
//Workaround 1
//Wait until the card has been removed from the range of NFC
//Then finish the activity
while(true)
{
try
{
iso.connect();
Thread.sleep(100);
iso.close();
}
catch (IOException | InterruptedException e)
{
//On this example, once we're done with the activity, we want to close it
//then onPause event will call disableReaderMode, at that moment we don't want a card to be in proximity
onCardRemoved();
break;
}
}
//End of Workaround 1
}
catch (IOException e)
{
Toast.makeText(this, "Tag disconnected. Reason: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
private void onCardRemoved()
{
this.finish();
}
#Override
protected void onResume()
{
super.onResume();
//Workaround 2
//Legacy nfc reader activation method, just enabled it but we won't use it
//(to fully support [e.g. OS version < v4.4.4] you must override onNewIntent(Intent intent) method as well)
//create intent/tag filters to be used with enableForegroundDispatch
PendingIntent pendingIntent = PendingIntent.getActivity(
this,
0,
new Intent(this, this.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
0
);
IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
tagDetected.addCategory(Intent.CATEGORY_DEFAULT);
IntentFilter[] writeTagFilters = new IntentFilter[]{tagDetected};
nfcAdapter.enableForegroundDispatch(this, pendingIntent, writeTagFilters, null);
//End of Workaround 2
//ReaderMode activation
Bundle options = new Bundle();
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 1000);//Presence check interval
final int READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK | NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS;
nfcAdapter.enableReaderMode(this, new NfcAdapter.ReaderCallback()
{
#Override
public void onTagDiscovered(Tag tag)
{
MainActivity.this.onTagDiscovered(tag);
}
}, READER_FLAGS, options);
}
#Override
protected void onPause()
{
nfcAdapter.disableReaderMode(this);
//Workaround 2
nfcAdapter.disableForegroundDispatch(this);
super.onPause();
}
}
I have developed small app to detect changes in network like on, off or connection change Wifi to Ethernet, whenever app closed or running in all cases.
Provided code working for me upto Nuget 7, when testing app in Oreo 8 background services not working when app terminated.
How can I get it work in Oreo?
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
Intent vpnServiceIntent = new Intent(getBaseContext(), MyService.class);
startForegroundService(vpnServiceIntent);}}
WifiReceiver.java file
public class WifiReceiver extends BroadcastReceiver {
static final String CONNECTIVITY_CHANGE_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (CONNECTIVITY_CHANGE_ACTION.equals(action)) {
if (!isConnected()) {
if (context != null) {
Toast.makeText(context," Not connected...",Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(context,"connected...",Toast.LENGTH_LONG).show();
}
}
}
}
MySevice.java file
public class MyService extends Service
{
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
IntentFilter filter = new IntentFilter();
filter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
WifiReceiver receiver =new WifiReceiver();
registerReceiver(receiver,filter);
return START_STICKY;
}
}
Manifest.xml file
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
<receiver android:name=".WifiReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
<service android:name=".MyService" />
</application>
Note: If I call startForeground(101, notification); in onCreate of MyService class, my above code is working but showing permanent notification icon on top most status bar, that I don't want at all.
since Android 8.0 (API level 26) it is basically impossible to run background service while app is not visible because of Battery optimizations and security reasons.
It sucks, many useful apps can not run and work normally.
They recommend to use ForegroundService which requires to show notification.
It would be almost okay, but these ForegroundServices also gets killed after some time.
To avoid killing them you need to make BatteryOptimization prompt so user would let service running in background without killing.
But it is not over yet... Services is still being killed on most of Manufactures like Samsung, Huawei and so on because they has they own badly implemented BatteryOptimizations running parallel with native one... and if user want some app avoid to be killed while running in background it has to go long way to settings find provider specific settings and let app run....
here is an example how to change these provider specific settings on Slack
I think it is worst thing that happened to Android.....
I need to connect to a device via wi-fi and I am using WifiManager.startScan() for this. In theory onReceive() should be called back, but this doesn't happen. The code just keeps waiting for the callback.
The thing is that this code actually works fine on a Samsung tablet with Android 8.1, but it doesn't on any phones that I have tried (Huawei Android 8.0 and Samsung Android 9).
Here is the relevant code:
public void Init()
{
try {
mainWifiObj = (WifiManager) act.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
WifiScanReceiver wifiReceiver = new WifiScanReceiver(act, logger, mainWifiObj);
act.getApplicationContext().registerReceiver(wifiReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
mainWifiObj.startScan();
}
catch (Exception ex)
{
...
}
}
public class WifiScanReceiver extends BroadcastReceiver {
...
public void onReceive(Context c, Intent intent) {
try {
...// doesn't get here
}
} catch (Exception e) {
...
}
...
}
And here are the all important manifest permissions I used:
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
The code doesn't generate any errors, it just silently fails to perform the callback.
Yes I found the answer. For a reason logical only to Android designers, you must enable Location Based Services for it to work. So it's not enough to give all the needed permissions to your app, but also go to settings and enable LBS.
The reason why it worked on the tablet is that LBS were enabled on it...but how is one supposed to keep all these dependencies under control?
Another thing to check is that you're not registering the broadcast receiver using
LocalBroadcastManager.getInstance(mContext).registerReceiver(broadcastReceiver, intentFilter);
use:
mContext.registerReceiver(broadcastReceiver, intentFilter);
UPDATE
I tried many codes, also from examples shown on the internet. each of them follows my approach. After many hours of testing, i came to the conclusion that on Android 6.0 there's no chance to achieve bluetooth discovery of unknown devices, we can only retrieve the bonded ones.
I'm pretty sure there's something with this android version.
if someone knows how to fix this, i would really appreciate any help.
Original Post
My code is working fine, but no devices get found.
i only receive DISCOVERY_STARTED and DISCOVERY_FINISHED, so no devices are found, but using system app these devices get found.
This is the code of my application, hope it can help.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bluetoothAdapter= BluetoothAdapter.getDefaultAdapter();
//other stuff...
IntentFilter filter=new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(myreceiver,filter);
}
final BroadcastReceiver myreceiver = new BroadcastReceiver(){
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.i("test","RECEIVED: "+ action);
if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
}
else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
}
if(BluetoothDevice.ACTION_FOUND.equals(action))
{
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.i("test", device.getName() + "\n" + device.getAddress());
}
}};
public void scanDevices(View v){
if (bluetoothAdapter.isEnabled()){
bluetoothAdapter.startDiscovery();
}
}
I already got permissions set:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Very simple solution:
1. Add FINE_LOCATION permission to manifest:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
2. Request FINE_LOCATION permission at runtime:
//since i was working with appcompat, i used ActivityCompat method, but this method can be called easily from Activity subclassess.
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION},MY_PERMISSION_REQUEST_CONSTANT);
3. Implement onRequestPermissionsResult method:
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSION_REQUEST_CONSTANT: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//permission granted!
}
return;
}
}
}
All this because Marshmallows requires this permission in order to use bluetooth for discovery.
Since this permission belongs to the Dangerous group of permissions, simply declaring it in the manifest doesn't work, we need the user's explicit consent to use the position (even if we don't need the position actually).
Goal: Build an Android app that discovers the names and addresses of BT devices within range and submits their values to a webservice. BT devices have not been previously bonded to the host device, I just want to poll everything as I walk about.
What I've done:
Pored over documentation.
Implemented a local instance of the host device's BT adapter.
Implemented a notification to enable BT if it isn't enabled.
Registered Broadcast Receivers and Intents to parse the ACTION_FOUNDs coming off of startDiscovery().
Registered BLUETOOTH and BLUETOOTH_ADMIN permissions in the manifest.
Things work (as tested with incremental console logging) up until startDiscovery().
Frustration:
startDiscovery() -- I suspect I am passing this in the wrong context. What context does this method need to be placed within to function properly?
If you have been able to get this method working, I would very much appreciate your wisdom.
UPDATE - here's a stripped down simplified version of the code that is causing me grief; this simplification recapitulates my error. This code runs, it throws no cat.log errors or other errors, it simply doesn't give any output.
package aqu.bttest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.widget.Toast;
public class BT2Activity extends Activity {
private BluetoothAdapter mBTA;
private SingBroadcastReceiver mReceiver;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//register local BT adapter
mBTA = BluetoothAdapter.getDefaultAdapter();
//check to see if there is BT on the Android device at all
if (mBTA == null){
int duration = Toast.LENGTH_SHORT;
Toast.makeText(this, "No Bluetooth on this handset", duration).show();
}
//let's make the user enable BT if it isn't already
if (!mBTA.isEnabled()){
Intent enableBT = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBT, 0xDEADBEEF);
}
//cancel any prior BT device discovery
if (mBTA.isDiscovering()){
mBTA.cancelDiscovery();
}
//re-start discovery
mBTA.startDiscovery();
//let's make a broadcast receiver to register our things
mReceiver = new SingBroadcastReceiver();
IntentFilter ifilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, ifilter);
}
private class SingBroadcastReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction(); //may need to chain this to a recognizing function
if (BluetoothDevice.ACTION_FOUND.equals(action)){
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// Add the name and address to an array adapter to show in a Toast
String derp = device.getName() + " - " + device.getAddress();
Toast.makeText(context, derp, Toast.LENGTH_LONG);
}
}
}
}
What context does this method need to be placed within to function properly.
To put it simply, you should use startDiscovery() when you want your application to discover local Bluetooth devices... for instance, if you wanted to implement a ListActivity that scans and dynamically adds nearby Bluetooth devices to a ListView (see DeviceListActivity).
Your usage of the startDiscovery() method should look something like this:
Define a class variable representing the local Bluetooth adapter.
BluetoothAdapter mBtAdapter = BluetoothAdapter.getDefaultAdapter();
Check to see if your device is already "discovering". If it is, then cancel discovery.
if (mBtAdapter.isDiscovering()) {
mBtAdapter.cancelDiscovery();
}
Immediately after checking (and possibly canceling) discovery-mode, start discovery by calling,
mBtAdapter.startDiscovery();
Be very careful in general about accidentally leaving your device in discovery-mode. Performing device discovery is a heavy procedure for the Bluetooth adapter and will consume a lot of its resources. For instance, you want to make sure you check/cancel discovery prior to attempting to make a connection. You most likely want to cancel discovery in your onDestroy method too.
Let me know if this helped... and if you are still having trouble, update your answer with your logcat output and/or any error messages you are getting, and maybe I can help you out a bit more.