I'am working on a RSSI scanner for Android. I want to archieve this via constantly scanning for BLE devices. So if the RSSI of a device should be figured out it needs to advertise via BLE. The measuring device start scanning in really short intervalls and as it finds the advertiser the RSSI can be read in the scan result.
That results in folowing code on the advertiser side:
fun beginAdvertise() {
val advertiser = BluetoothAdapter.getDefaultAdapter().bluetoothLeAdvertiser
val settings = AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
.setConnectable(false)
.build()
val data = AdvertiseData.Builder()
.setIncludeDeviceName(true)
.build()
val advertisingCallback = object : AdvertiseCallback() {
override fun onStartFailure(errorCode: Int) {
Log.e("BLE", "Advertising onStartFailure: " + errorCode);
super.onStartFailure(errorCode)
}
}
advertiser.startAdvertising(settings, data, advertisingCallback);
}
The function for the scanning device gets called every 20ms (I tried much longer delays) and looks like this:
private val onBluetoothLeDeviceFound = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult?) {
result?.let {...}}
fun startScan() {
val scanner = mBluetoothAdapter?.bluetoothLeScanner
val scanSetting =
ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE).build()
scanner?.startScan(
null,
scanSetting,
onBluetoothLeDeviceFound
)
}
This code is working perfectly on my Pixel 2 devices. But if I try it on Pixel 1 (one on Android 9, the other on Android 10) devices the onBluetoothLeDeviceFound callback does not get called.
I have these permissions:
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCTION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
I really don'T know what I should try out next. Thank you for your help!
The solution was simple, but time consuming. You dont just need to have the location permissions, but also have to enable location services! On the Pixel 2 devices, they were randomly turned on, so it worked there, but not on the Pixel 1.
Related
I'm a beginner in the BLE area.
Using the BLE scanner, I would like to use the ScanFilter API to filter the results using the serial port UUID: 00001101-0000-1000-8000-00805f9b34fb(my target are bluetooth printers).
Currently, after starting the BLE scan, I'm not getting any results in the onScanResult method of the ScanCallback object.
Without using the filter, I'm receiving bluetooth devices correctly. I noticed that if I try to get the device UUIDs into onScanResult it returns null, while if I run the method fetchUuidsWithSdp the UUIDs are returned correctly.
Here my current code to start scanning:
val serviceUuidMaskString = "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"
val parcelUuidMask = ParcelUuid.fromString(serviceUuidMaskString)
val filter = ScanFilter.Builder().setServiceUuid(
ParcelUuid(UUID.fromString("00001101-0000-1000-8000-00805f9b34fb")), parcelUuidMask
).build()
val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_BALANCED).build()
handler.postDelayed({
bluetoothAdapter.bluetoothLeScanner.stopScan(bleCallback)
}, 15000)
bluetoothAdapter.bluetoothLeScanner.startScan(listOf(filter), settings, bleCallback)
And here the ScanCallback:
object : ScanCallback() {
#SuppressLint("MissingPermission")
override fun onScanResult(callbackType: Int, result: ScanResult?) {
result?.let {
it.device.fetchUuidsWithSdp()
Log.i(
TAG,
"BLE device: ${it.device?.name.orEmpty()}\n UUIDS: ${it.device?.uuids?.joinToString()}"
)
Sorry for my bad english.
Thanks in advance.
I need to take SSID of connected WiFi and first i tried this:
val wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiInfo = wifiManager.connectionInfo
textField.text = wifiInfo.ssid
But wifiInfo.ssid returns <unknown ssid> when testing on emulator with API 30 or Xiaomi Redmi Note 8 Pro(Android 11), just as in emulator with API 27 wifiInfo.ssid returns correct SSID.
Then i tried this:
val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build()
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities)
//wifiInfo = networkCapabilities.transportInfo as WifiInfo
Log.d("%%%", "onCapabilitiesChanged: ${networkCapabilities.transportInfo}")
}
}
connectivityManager.requestNetwork(request, networkCallback)
connectivityManager.registerNetworkCallback(request, networkCallback)
But networkCapabilities.transportInfo always null
EDIT:
Permissions in manifest file
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
Request location permission in OnCreate():
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1)
And it successfully granted.
SOLVE 1:
I just forget to on Geolocation on my phone)
It solves problem while i using first method taking wifiinfi by wifiManager.connectionInfo but when i use second one by networkCapabilities.transportInfo it still returns null, and since first method is Deprecated in java maybe it is not good to use it.
Then, I would be glad to have your comments about the second method.
Google assumes that knowing ssid means knowing location.
Go for fine location permission and you will see.
I try to get my currend Wifi SSID on Android 10.
Code from Android 9 or lower does not work anymore.
Is there any sample for Android 10.
Regards Hacki
Since API 26 (Android 8 Oreo) you need to obtain the user location permission in order to get the wifi name (SSID), which is why also in Android 9 (API 28), Android 10 (API 29) or Android 11 (API 30) and newer you may get <unknown name> as the SSID returned or 02:00:00:00:00:00.
For that, in AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
I will provide some demo code written in Kotlin.
For declaring your permission request success code, we store it in a companion object (Kotlin way for storing constants) inside the class we are testing from (MainActivity in this case) or you may define a class explicitly for constants, which is actually a common practice.
class MainActivity : AppCompatActivity() {
...
companion object {
const val PERMISSION_CODE_ACCEPTED = 1
const val PERMISSION_CODE_NOT_AVAILABLE = 0
}
...
}
In your testing activity:
when(requestLocationPermission()){
MainActivity.PERMISSION_CODE_ACCEPTED -> getWifiSSID()
}
For checking an requesting the permission:
fun requestLocationPermission(): Int {
if (ContextCompat.checkSelfPermission(this,
android.Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
android.Manifest.permission.ACCESS_FINE_LOCATION)) {
} else {
// request permission
ActivityCompat.requestPermissions(this,
arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION),
MainActivity.PERMISSION_CODE_ACCEPTED)
}
} else {
// already granted
return MainActivity.PERMISSION_CODE_ACCEPTED
}
// not available
return MainActivity.PERMISSION_CODE_NOT_AVAILABLE
}
For actually getting the SSID (wifi name):
fun getWifiSSID() {
val mWifiManager: WifiManager = (this.getApplicationContext().getSystemService(Context.WIFI_SERVICE) as WifiManager)!!
val info: WifiInfo = mWifiManager.getConnectionInfo()
if (info.getSupplicantState() === SupplicantState.COMPLETED) {
val ssid: String = info.getSSID()
Log.d("wifi name", ssid)
} else {
Log.d("wifi name", "could not obtain the wifi name")
}
}
Tested on the emulator on API 29 (Android 10).
2020-10-04 15:35:28.625 13013-13013/com.example.myapplication D/wifi name: "AndroidWifi"
Set his two permissions requests into your project manifest file:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"\>
Than you have to set Application permission manually by using Settings screen of Android.
so recently my friends and i thought about creating an app that allows somewhat of realtime communication between the connected devices, without the need of a webserver. More explicitily, it should be an app, where a single device (master/host) create a game/session and multiple devices (slaves/clients) can join. after establishing all necessary connections (4-5 clients), the host should be able to broadcast data to the clients. Hence i researched a bit and if i understand it correctly the best guess for android are either the WiFi direct oder the google nearby connections api.
Q1. Is this the most simple approach to the desired goal ? or is this already too deep?
So i played a bit around with the connections api, i made a simple application and just used the code from the Nearby Documentation. Since im new to Kotlin, it could also be a rather simple mistake, however after a 2 hours, i swapped back to java with the same error. when the clients try to discover the host, they triggered their OnFailureListener. I tried to search for a solution online (including SO), but i could not find any useful information.
Im testing the application on a HTC ONE M8 and a Samsung Galaxy S7. To Ensure the Nearby Connection API features should work I also downloaded 2 example apps and those worked just fine. I tried how these handled the usage of the api but could not find the important part.
Q2. Where do i use the API wrong ? Or is it really just a error in the coding ?
MainActivity.kt
private const val TAG = android.R.attr.packageNames.toString() + "/Filter"
class MainActivity : AppCompatActivity() {
private lateinit var connectionClient : ConnectionsClient
private val payloadCallback = object : PayloadCallback() {
override fun onPayloadReceived(p0: String, p1: Payload) {
Toast.makeText(applicationContext, "Payload Received", Toast.LENGTH_SHORT).show()
}
override fun onPayloadTransferUpdate(p0: String, p1: PayloadTransferUpdate) {
Toast.makeText(applicationContext, "Payload Transfer Update", Toast.LENGTH_SHORT).show()
}
}
private val connPoint = object : ConnectionLifecycleCallback() {
override fun onConnectionInitiated(p0: String, p1: ConnectionInfo) {
connectionClient.acceptConnection(p0, payloadCallback)
Log.i(TAG, "OnConnectionInitiated")
}
override fun onConnectionResult(p0: String, p1: ConnectionResolution) {
when(p1.status.statusCode){
ConnectionsStatusCodes.STATUS_OK -> Log.i(TAG, "ConnectionsStatusCodes STATUS_OK")
ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED -> Log.i(TAG, "ConnectionsStatusCodes STATUS_CONNECTION_REJECTED")
ConnectionsStatusCodes.STATUS_ERROR -> Log.i(TAG, "ConnectionsStatusCodes STATUS_ERROR")
else -> Log.i(TAG, "ConnectionsStatusCodes STATUS_UNKNOWN")
}
}
override fun onDisconnected(p0: String) {
Log.i(TAG, "onDisconnected $p0")
}
}
private val endPoint = object : EndpointDiscoveryCallback() {
override fun onEndpointFound(p0: String, p1: DiscoveredEndpointInfo) {
Log.i(TAG, "onEndpointFound ID: $p0 Name: ${p1.endpointName} ")
connectionClient.requestConnection(p1.endpointName, p0, connPoint)
.addOnSuccessListener {
Log.i(TAG, "OnSuccessListener requestConnection")
}
.addOnFailureListener {
Log.i(TAG, "OnFailureListener requestConnection")
}
}
override fun onEndpointLost(p0: String) {
Log.i(TAG, "$p0 disconnected")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
connectionClient = Nearby.getConnectionsClient(this.applicationContext)
//Toast.makeText(applicationContext, connectionClient.instanceId, Toast.LENGTH_SHORT).show()
setButtonOnClick()
}
override fun onStop() {
connectionClient.stopAllEndpoints()
connectionClient.stopAdvertising()
connectionClient.stopDiscovery()
super.onStop()
}
private fun setButtonOnClick(){
val create = findViewById<Button>(R.id.create_btn)
val join = findViewById<Button>(R.id.join_btn)
create.setOnClickListener{ _ -> CreateGroup()}
join.setOnClickListener{ _ -> JoinGroup()}
Log.i(TAG, "On Click Listener set")
}
private fun CreateGroup(){
Log.i(TAG, "Starting Advertising")
connectionClient
.startAdvertising(android.os.Build.MODEL,
packageName.toString(),
connPoint,
AdvertisingOptions.Builder().apply{
setStrategy(Strategy.P2P_CLUSTER)
}.build())
.addOnSuccessListener {
OnSuccessListener<Void> {
Log.i(TAG, "OnSuccessListener CreateGroup() was triggered")
}
}
.addOnFailureListener {
OnFailureListener {
Log.i(TAG, "OnFailureListener CreateGroup() was triggered")
}
}
}
private fun JoinGroup(){
Log.i(TAG, "Starting Discovering")
connectionClient.startDiscovery(packageName.toString(),
endPoint,
DiscoveryOptions.Builder().apply{
setStrategy(Strategy.P2P_CLUSTER)
}.build())
.addOnSuccessListener {
OnSuccessListener<Void> {
Log.i(TAG, "OnSuccessListener JoinGroup() was triggered")
}
}
.addOnFailureListener {
OnFailureListener {
Log.i(TAG, "OnSuccessListener JoinGroup() was triggered")
}
}
}
}
Android Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.testapplication">
<!-- Required for Nearby Connections -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Optional: only required for FILE payloads -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
Okay, after figuring out how to correctly setup the Listeners using Kotlin i found out that i got the exception of a missing permission, 01-20 21:11:14.269 1058-1058/com.example.testapplication I/16843649/Filter: 8034: MISSING_PERMISSION_ACCESS_COARSE_LOCATION, which i thought was strange since its in the manifest. However i went to the normal app settings and turned on the permissions manually, and it works now.
I am checking all permisions from the Manifest with the code bellow. Now I added USE_BIOMETRIC permission, because I want to use fingerprint, but this permission is not granted and I don't know why.
I know that USE_BIOMETRIC is normal permission so it shouldn't be asked and should be granted when it's in the Manifest, but it's not.
Checking for other permissions is working and all are granted or asked for it, only this one is not.
I am testing app on 2 phones, emulated Google pixel with Android 8.0 and API 26 and physical Xiaomi Redmi 5 with Android 8.1 and API 27.
Both phones are using fingerprint to unlock screen.
Permisions in the manifest:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
Code that loads permissions from the manifest and check if is granted or not:
fun checkAndRequestPermissions(): Boolean{
var listPermissionsNeeded = ArrayList<String>()
val permissions = retrievePermissions(activity.baseContext)
permissions.forEach {
if(ContextCompat.checkSelfPermission(activity.baseContext, it) != PackageManager.PERMISSION_GRANTED){
listPermissionsNeeded.add(it)
Log.d("Missing permission", it)
}
}
if (!listPermissionsNeeded.isEmpty()) {
val array = arrayOfNulls<String>(listPermissionsNeeded.size)
listPermissionsNeeded.toArray(array)
ActivityCompat.requestPermissions(activity, array, ConstantsStorage.ACTIVITY_REQUEST_PERMISSIONS_CODE)
return false
}
return true
}
companion object {
/**
* Retrieves permissions listed in the manifest file
* #param context Context
* #return Returns String array of permissions
*/
fun retrievePermissions(context: Context): Array<String> {
try {
return context
.packageManager
.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS)
.requestedPermissions
} catch (e: PackageManager.NameNotFoundException) {
throw RuntimeException("This should have never happened.", e)
}
}
}
Thank you for your help
According to the new policy in Google Console, you couldn't use this permission on the Manifest. So you should Request and Check in Activity.
You can use BiometricManager to check it:
val biometricManager = BiometricManager.from(this)
when (biometricManager.canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)) {
BiometricManager.BIOMETRIC_SUCCESS ->
Log.d("MY_APP_TAG", "App can authenticate using biometrics.")
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE ->
Log.e("MY_APP_TAG", "No biometric features available on this device.")
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE ->
Log.e("MY_APP_TAG", "Biometric features are currently unavailable.")
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
// Prompts the user to create credentials that your app accepts.
val enrollIntent = Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply {
putExtra(Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
}
startActivityForResult(enrollIntent, REQUEST_CODE)
}
}
Note: It's very important to add suitable import
import androidx.biometric.BiometricManager;