I developed an App which contain ble function. And this app operated well on my development phone. But when I use this app on Samsung galaxy S10e, bluetooth is not working. And I found BluetoothLeScanner is not called. My development phone is galaxy S8 and its Version is Android 9.0(Pie), API 28. And another low version development phone(like galaxy 5, galaxy note 4) operated well too.
But galaxy S10e is same OS with galaxy S8(Android 9.0 Pie, API 28). I wonder why the app is not operate well on galaxy S10e.
I declared permission like that
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
And BluetoothLeScanenr is called here.
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public boolean BleDeviceScan(boolean enable)
{
logD(TAG, "BleDeviceScan ( " + enable + " )");
if(mBluetoothAdapter == null)
{
return false;
}
logD(TAG, "mBluetoothAdapter : " + mBluetoothAdapter.toString());
if(mBluetoothAdapter.isEnabled())
{
if (enable)
{
Log.d(TAG, "BleDeviceScan Build.VERSION.SDK_INT < MIN_SDK_INT - ELSE");
timerFlag = true;
timer = new Timer();
bleTimer();
mLEScanner.startScan(filters, settings, mScanCallback);
mListener.bleScanStart();
}
else
{
deviceShowCount = 0;
timerFlag = false;
bHandler.obtainMessage(1).sendToTarget();
mLEScanner.stopScan(mScanCallback);
mListener.bleScanStop();
}
logD(TAG, "BleDeviceScan Fin");
return true;
}
return false;
}
this function is not called in galaxy S10e.
mLEScanner.startScan(filters, settings, mScanCallback);
Should I declare something to use BluetoothLeScanner on galaxy S10e? What is the problem?
Based on your description, scanning does not start because mBluetoothAdapter.isEnabled() always returns false. Can you verify that is the case?
If the above is true, do settings on the device say that Bluetooth is turned on? If so, can detect BLE devices with an off the shelf app like LightBlue or BeaconScope?
Related
I've build an android app which has to scans for Bluetooth devices every 15 minutes for a duration of 15 seconds. I expect my schedulejob to be triggered every 15 minutes (the minimum allowed time by android 7+), get a recent geolocation, scan for 15 seconds, and then send that list of devices with that specific geolocation to my backend. This procedure should also run even if the phone is in standby mode.
I've added logging to every step in the process to see what's actually being triggered and what not. My scheduleJob is actually running every 15 minutes as expected, but the scanning for BLE devices isn't being done. No error messages are displayed, but I can clearly see the scan process just isn't running.
To setup some tests, I've added a manual trigger to scan for devices and run the full process, when I do this, everything works as expected. So I think I can state that my setup to confirm permissions is done correctly and my location helper class is working properly (I confirmed this during the schedulejob process as well, I have successfully gotten a good location). I can confirm that the bluetooth system on my phone is working properly as the manual trigger does find my devices.
So it just seems like the startScan isn't running as I'd expect during the schedulejob. I've tested this on an Android 6, Android 7 and an Android 10, on the android 6 the scan does seem to work as expected even in standby modes, from the Android 7 onwards it doesn't work in standby mode anymore.
When the user keeps the app open and active for 15 minutes, the startscan does seem to trigger on the android 10.
For my scheduleJob I'm calling following method:
public void scheduleJob(){
ComponentName componentName = new ComponentName(this, AppService.class);
JobInfo info = new JobInfo.Builder(123, componentName)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPersisted(true)
// 1000 * 60 * 2.5 => 2.5 minutes => Android will change this automatically to the minimum allowed => 15 minutes
.setPeriodic(2500 * 60)
.build();
}
For my permissions I have following:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
As for my startLeScan function, which I can confirm is being triggered
private void init() {
if(mMapBleScanCallback == null) {
mMapBleScanCallback = new ConcurrentSkipListMap<>();
}
if(mHandler == null) {
mHandler = new Handler();
}
if(mBluetoothAdapter == null) {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
if(mBluetoothLeScanner == null) {
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
}
if(mScanSettingsBuilder == null) {
mScanSettingsBuilder = new ScanSettings.Builder();
mScanSettingsBuilder.setScanMode(ScanSettings.SCAN_MODE_BALANCED);
}
}
public void startLeScan() {
Log.d(TAG, "startLeScan");
if(mBluetoothLeScanner == null) {
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
}
if(mBluetoothLeScanner != null && !mIsScanning) {
mIsScanning = true;
notifyCallback(Constants.BLE.CALLBACK_SCAN_START, null);
//mBluetoothLeScanner.startScan(getScanFilters(), mScanSettingsBuilder.build(), mScanCallback);
// I've set my filter to null
// my scansettings are set to ScanSettings.SCAN_MODE_BALANCED during init
mBluetoothLeScanner.startScan(null, mScanSettingsBuilder.build(), mScanCallback); // I've set my filters to null
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
stopLeScan();
}
}, Constants.BLE.SCAN_PERIOD);
}
}
As for my callback, I've added logging when a device is detected. I can see in my logging during manual scan that devices are found, but not during the schedulejob, none are shown...
private ScanCallback mScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
Log.d(TAG, "onScanResult: " + result.toString());
super.onScanResult(callbackType, result);
prepareScanResult(result);
}
#Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.d(TAG, "onScanFailed: errorcode: " + errorCode);
}
};
Here is an image of some of the logging:
I found an article (ref. to point 1) which mentions that Samsung phones with Android 7+ don't allow scanning without a minimum of 1 scanfilter. And indeed, after implementing at least one scanfilter, everything started to work properly.
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);
I'm trying to use the function BluatoothLeScanner.startScan instead of the deprecated one BluetoothAdapter.startLeScan.
Yesterday I updated my Nexus 5 to Android 6.0 and since that moment my app does not work anymore.
I firstly add the preferences required ACCESS_COARSE_LOCATION as found here, https://developer.android.com/about/versions/marshmallow/android-6.0-changes.html#behavior-hardware-id.
Then I added the permission as described here: https://developer.android.com/training/permissions/requesting.html.
But at the end it seems not working, it does not send back the ble devices.
This is my code:
manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.stm.sensitronapp">
<uses-sdk android:maxSdkVersion="23"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>`
DeviceScanActivity
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
if (ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED){
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_COARSE_LOCATION)) {
} else {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
MY_PERMISSIONS_REQUEST_ACCESS_COARSE);
}
}
// Device scan callback.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_COARSE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
mScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
mLeDeviceListAdapter.addDevice(result.getDevice());
mLeDeviceListAdapter.notifyDataSetChanged();
}
};
}
}
}
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
mSwipeRefreshLayout.setRefreshing(true);
mLeDeviceListAdapter.clear();
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
if(ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION ) == PackageManager.PERMISSION_GRANTED) {
mBluetoothLeScanner.startScan(mScanCallback);
}
}
EDIT: to solve this problem I only turned on the GPS. It is easy to do it programmatically in this way.
if permissions granted, have a try: turn ON the GPS.
Is you app prompting for Location permission on startup? If it's not, handle the code somewhere else so that it is being prompted.
Also you can check this to test if your app is working fine:
Open Settings > Apps > YourApplication > Permissions
and enable Location and then try to scan for results.
Location will be listed under permissions only if you have provided ACCESS_COARSE_LOCATION on manifest.
Using the solutions provided above works but the side effect is that you have to have location services turned on for something that doesn't need it. An ugly and unsatisfying work around is to specify the target version in your manifest to
android:targetSdkVersion="21"
It allows scanning on my Nexus 7 even though the installed version is 6.0.1. I do not know what the side effects are of targeting a lower version than the installed version but at least scanning works. Might be the only solution for GPS-less devices (if such devices exist).
Google should be crucified for this.
One - not perfect answer, is that you can still use the same old method BT scan method, once you have the new runtime Location permission enabled.
mBluetoothAdapter.startDiscovery();
.......
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
mDeviceList.add(device);
}
}
};
It's an old question, but I will answer to help someone.
Unfortunately, the combination of ACCESS_COARSE_LOCATION and targetSdkVersion 22 does not work on some devices.This is not a good method, but I have solved it in the following way without using runtime permissions (ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION)
Set your 'targetSdkVersion' to 19 (I think maybe api19 ~ api22 will be possible)
Add the following permission to your manifest file
<uses-permission android:name="android.permission.READ_OWNER_DATA" />
<uses-permission android:name="android.permission.WRITE_OWNER_DATA" />
tested to Android 4.4 ~ 7.1.1
Set your 'minSdkVersion' to 18
targetSdkVersion 22
I upgraded to Android 6 and my applications who use Bluetooth doesn't work with this new API version. It's the same problem with application on Play Store: Bluetooth spp tools pro (good application to view if bluetooth works) which doesn't discovery of devices.
The problem seems to be in Bluetooth discovery:
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothAdapter.startDiscovery()
Log.i("BLUETOOTH", String.valueOf(mBluetoothAdapter.isDiscovering())); // Return false
My applications work well with Android 4/5 and I followed : http://developer.android.com/guide/topics/connectivity/bluetooth.html
Staring with Android 6.0 it is not enough to include permissions on manifest.
You have to ask the user explicitly about each permission that is considered "dangerous".
BluetoothDevice.ACTION_FOUND requires BLUETOOTH and ACCESS_COARSE_LOCATION permissions
http://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#ACTION_FOUND
The ACCESS_COARSE_LOCATION
http://developer.android.com/reference/android/Manifest.permission.html#ACCESS_COARSE_LOCATION
is a "dangerous" permission and therefore you have to ask for it using requestPermission before doing actual discovery.
public void doDiscovery() {
int hasPermission = ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION);
if (hasPermission == PackageManager.PERMISSION_GRANTED) {
continueDoDiscovery();
return;
}
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{
android.Manifest.permission.ACCESS_COARSE_LOCATION},
REQUEST_COARSE_LOCATION_PERMISSIONS);
}
then on you will get the user answer on onRequestPermissionsResult
#Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_COARSE_LOCATION_PERMISSIONS: {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
continueDoDiscovery();
} else {
Toast.makeText(this,
getResources().getString(R.string.permission_failure),
Toast.LENGTH_LONG).show();
cancelOperation();
}
return;
}
}
}
To work with previous versions of android you should use compatibility libraries and make the calls using ActivityCompat
I've spent some time investigating the problem.
Created bug report on Android bug tracker here
The problem is that system does not forward BluetoothDevice.ACTION_FOUND intents to the registered BroadcastReceiver. Logcat shows lines like this:
10-16 07:34:09.147 786-802/? W/BroadcastQueue﹕ Permission Denial: receiving Intent { act=android.bluetooth.device.action.FOUND flg=0x10 (has extras) } to ProcessRecord{5ce2d92 21736:com.example.mvl.bluetoothtest/u0a74} (pid=21736, uid=10074) requires android.permission.ACCESS_COARSE_LOCATION due to sender com.android.bluetooth (uid 1002)
Which themes for me that the application needs android.permission.ACCESS_COARSE_LOCATION permission to receive this intents. i personaly don't understand why I need that permission to get the Bluetooth devices around.
So if you add this permission to you Manifest, then it should work with one more precondition - You have to set target SDK and compile with SDK not higher, then 22.
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
When checking the source code in GattService.java,you will find some code comments in method onScanResult:
// Do no report if location mode is OFF or the client has no location permission
// PEERS_MAC_ADDRESS permission holders always get results
if (hasScanResultPermission(client) && matchesFilters(client, result)) {
try {
ScanSettings settings = client.settings;
if ((settings.getCallbackType() &
ScanSettings.CALLBACK_TYPE_ALL_MATCHES) != 0) {
app.callback.onScanResult(result);
}
} catch (RemoteException e) {
Log.e(TAG, "Exception: " + e);
mClientMap.remove(client.clientIf);
mScanManager.stopScan(client);
}
}
this clarified what is needed to get a Bluetooth LE advertising report.
I see a lot of questions that it's impossible to end call programmatically in Android.
At the same time, I see a lot of dialer apps in googleplay market where you can activate the call and drop it also. How do they work?
Edit: I've read somewhere that my app has to be system app. Then how to make it system, and what is the difference between system and user apps?
You do not need to be a system app. First, create package com.android.internal.telephony in your project, and put this in a file called "ITelephony.aidl":
package com.android.internal.telephony;
interface ITelephony {
boolean endCall();
void answerRingingCall();
void silenceRinger();
}
Once you have that, you can use this code to end a call:
TelephonyManager telephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
Class clazz = Class.forName(telephonyManager.getClass().getName());
Method method = clazz.getDeclaredMethod("getITelephony");
method.setAccessible(true);
ITelephony telephonyService = (ITelephony) method.invoke(telephonyManager);
telephonyService.endCall();
You could use this inside a PhoneStateListener, for example. For this to work, you require permissions in manifest:
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
Edit: Apologies for horrible formatting, I still can't figure out how to properly do code blocks here :/
For Android P (since Beta 2) and above, there is finally a formal API for endCall:
https://developer.android.com/reference/android/telecom/TelecomManager#endCall()
The ANSWER_PHONE_CALLS permission is required in manifest:
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
With the permission, for API level 28 or above:
TelecomManager tm = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
if (tm != null) {
boolean success = tm.endCall();
// success == true if call was terminated.
}
At the same time the original endCall() method under TelephonyManager is now protected by MODIFY_PHONE_STATE permission, and can no longer be invoked by non-system Apps by reflection without the permission (otherwise a Security Exception will be triggered).
For Information.
May be of use in some situations. There is a potential workaround using the InCallService class. Most of the required information is here.https://developer.android.com/reference/android/telecom/InCallService.html#onCallRemoved(android.telecom.Call)
It does require setting your app as the default phone app and ensuring the following is granted.
<uses-permission android:name="android.permission.CALL_PHONE" />
If you implement your own class extending InCallService then when a call starts the call binds to your app and you get the call information through the onCallAdded() function. You can then simply call.disconnect() and the call will end.
Cut Call for the Api 28+
private void cutCall(){
ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.READ_PHONE_STATE }, PHONE_STATE);
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (requestCode == PHONE_STATE) {
ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.ANSWER_PHONE_CALLS }, ANSWER_CALLS);
} else if (requestCode == ANSWER_CALLS) {
cutTheCall;
}
}
}
//This code will work on Android N (Api 28 and Above)
private boolean cutTheCall() {
TelecomManager telecomManager = (TelecomManager) getApplicationContext().getSystemService(TELECOM_SERVICE);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED || telecomManager == null) {
return false;
}
if (telecomManager.isInCall()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
callDisconnected = telecomManager.endCall();
}
}
return true;
}
SilenceRinger() does not work for android 2.3+ versions. Just comment it, other code will work fine.
Hope this works for you!
Along with Adding android telephony interface and a broadcast receiver, you will also need to add android manifest receiver entry with the action android.intent.action.PHONE_STATE for the reciever you want to handle intent.
You will get compile time error if you add
uses-permission android:name="android.permission.MODIFY_PHONE_STATE`
in your manifest file. But even if we remove this, it automatically rejects the incoming calls.
public static boolean isCallActive(Context context){
AudioManager manager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
if(manager.getMode()==AudioManager.MODE_IN_CALL || manager.getMode()==AudioManager.MODE_IN_COMMUNICATION){
return true;
}
return false;
}
Just to add to #headuck's answer. For API 28, you also need to add:
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
then request the permission in your activity. In total I requested these permissions to make it work (READ_PHONE_STATE, CALL_PHONE, ANSWER_PHONE_CALLS, READ_CONTACTS, READ_CALL_LOG)
You can end calls using Telecom manager. I tested it and it worked.
You need permission ANSWER_PHONE_CALLS to do so. Even though it hints to answered calls I had it ending a call made from this phone. And this works for modern Androids.
TelecomManager telecomManager = (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
telecomManager.endCall();