Am running a foreground service to scan ble devices which is working fine when the phone is not locked. But when the phone is locked, the scanner is unable to detect any devices near by. The scanned count is always 0 when the phone is locked. I have also added the filter for my scanner but still no fortune. Looking for some help.
//adding filters of the manufacturer and the uuid
fun startScan(){
settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
val builder = ScanFilter.Builder()
builder.setManufacturerData(0x004c, byteArrayOf())
val manufactureFilter= builder.build()
val uuidBuilder = ScanFilter.Builder()
val serviceUuidString = "f8c62883-xxxx-xxxx-xxxx-430326af8bd0"
val serviceUuidMaskString = "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"
val parcelUuid: ParcelUuid = ParcelUuid.fromString(serviceUuidString)
val parcelUuidMask: ParcelUuid = ParcelUuid.fromString(serviceUuidMaskString)
uuidBuilder.setServiceUuid(parcelUuid, parcelUuidMask)
val uuidFilter = uuidBuilder.build()
filters = ArrayList<ScanFilter>()
filters.add(manufactureFilter)
filters.add(uuidFilter)
scanLeDevice(true)
}
//to start the ble scan for a short period
fun scanLeDevice(enable: Boolean) {
if (enable) {
Log.i(TAG, "Scanning started")
if(beaconCollectionTimer != null){
beaconCollectionTimer?.cancel()
}
beaconCollectionTimer = Timer()
beaconCollectionTimer?.schedule(object : TimerTask(){
override fun run() {
scanLeDevice(false)
}
}, SCANNING_INTERVEL)
bluetoothAdapter.getBluetoothLeScanner()
.startScan(filters, settings, mScanCallback)
} else {
Log.i(TAG, "scanning stopped")
if (bluetoothAdapter.getBluetoothLeScanner() != null) {
bluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback)
}
isScanning = false
}
}
After trying various libraries to get my scanner work properly, I realized that the issue is not in the code but with the battery saver. All I did is removed the app from the battery optimization apps list and my scanner started working as expected. Even after the screen is locked am able to run the bleScanner and detect the near by devices.
Related
UPDATE: Added Main Activity code which contains Bluetooth permissions logic
I'm trying to utilize Android's CompanionDeviceManager API to find nearby bluetooth (non LE) devices on my Pixel 5 running Android 13, but it only ever seems to find nearby WiFi networks. I'm suspicious that the deviceFilter isn't working properly.
Initially, my code to configure the BluetoothDeviceFilter looked like this:
private val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder()
// Match only Bluetooth devices whose name matches the pattern
.setNamePattern(Pattern.compile("(?i)\\b(Certain Device Name)\\b"))
.build()
private val pairingRequest: AssociationRequest = AssociationRequest.Builder()
// Find only devices that match our request filter
.addDeviceFilter(deviceFilter)
// Don't stop scanning as soon as one device matching the filter is found.
.setSingleDevice(false)
.build()
With this code, however, no devices ever appear within the system generated Companion Device Pairing screen. The spinner spins until timeout
Thinking maybe my regex was unintentionally too restrictive, I changed the filter to use a regexp that allows everything, like so:
.setNamePattern(Pattern.compile(".*"))
But even this filter fails to allow any nearby bluetooth devices to appear in the Pairing screen.
When I intentionally don't add any filter all I see are WiFi networks, so the Companion Device Manager can work, it's just seemingly misconfigured for Bluetooth results.
private val pairingRequest: AssociationRequest = AssociationRequest.Builder()
// No filter, let's see it all!
.setSingleDevice(false)
.build()
Using the Android OS's system Bluetooth menu I clearly see there are Bluetooth devices within range of my device, and I can even connect to them, but the same devices never appear within my app.
What am I doing wrong that's causing no nearby Bluetooth devices to appear in my CompanionDeviceManager Pairing Screen?
Code below:
HomeFragment.kt
class HomeFragment : Fragment() {
//Filter visible Bluetooth devices so only Mozis within range are displayed
private val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder()
// Match only Bluetooth devices whose name matches the pattern.
.setNamePattern(Pattern.compile(BLUETOOTH_DEVICE_NAME_REGEX_TO_FILTER_FOR))
.build()
private val pairingRequest: AssociationRequest = AssociationRequest.Builder()
// Find only devices that match this request filter.
.addDeviceFilter(deviceFilter)
// Don't stop scanning as soon as one device matching the filter is found.
.setSingleDevice(false)
.build()
private val deviceManager: CompanionDeviceManager by lazy {
requireContext().getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager
}
private val executor: Executor = Executor { it.run() }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
setupPairingButton()
}
/**
* This callback listens for the result of connection attempts to our Mozi Bluetooth devices
*/
#Deprecated("Deprecated in Java")
#SuppressLint("MissingPermission")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
SELECT_DEVICE_REQUEST_CODE -> when (resultCode) {
Activity.RESULT_OK -> {
// The user chose to pair the app with a Bluetooth device.
val deviceToPair: BluetoothDevice? =
data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)
deviceToPair?.createBond()
}
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
private fun setupPairingButton() {
binding.buttonPair.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
/**
* This is the approach to show a pairing dialog for Android 33+
*/
deviceManager.associate(pairingRequest, executor,
object : CompanionDeviceManager.Callback() {
// Called when a device is found. Launch the IntentSender so the user
// can select the device they want to pair with
override fun onAssociationPending(intentSender: IntentSender) {
intentSender.let { sender ->
activity?.let { fragmentActivity ->
startIntentSenderForResult(
fragmentActivity,
sender,
SELECT_DEVICE_REQUEST_CODE,
null,
0,
0,
0,
null
)
}
}
}
override fun onAssociationCreated(associationInfo: AssociationInfo) {
// Association created.
// AssociationInfo object is created and get association id and the
// macAddress.
var associationId = associationInfo.id
var macAddress: MacAddress? = associationInfo.deviceMacAddress
}
override fun onFailure(errorMessage: CharSequence?) {
// Handle the failure.
showBluetoothErrorMessage(errorMessage)
}
})
} else {
/**
* This is the approach to show a pairing dialog for Android 32 and below
*/
// When the app tries to pair with a Bluetooth device, show the
// corresponding dialog box to the user.
deviceManager.associate(
pairingRequest,
object : CompanionDeviceManager.Callback() {
override fun onDeviceFound(chooserLauncher: IntentSender) {
startIntentSenderForResult(
chooserLauncher,
SELECT_DEVICE_REQUEST_CODE,
null,
0,
0,
0,
null
)
}
override fun onFailure(error: CharSequence?) {
// Handle the failure.
showBluetoothErrorMessage(error)
}
}, null
)
}
}
}
companion object {
private const val SELECT_DEVICE_REQUEST_CODE = 0
private const val BLUETOOTH_DEVICE_NAME_REGEX_TO_FILTER_FOR = "(?i)\\bCertain Device Name\\b"
}}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val enableBluetoothIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
private var bluetoothEnableResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
binding.loadingSpinner.hide()
when (result.resultCode) {
Activity.RESULT_OK -> {
Snackbar.make(
binding.root,
resources.getString(R.string.bluetooth_enabled_lets_pair_with_your_mozi),
Snackbar.LENGTH_SHORT
).show()
}
Activity.RESULT_CANCELED -> {
Snackbar.make(
binding.root,
getString(R.string.without_bluetooth_you_cant_pair_with_your_mozi),
Snackbar.LENGTH_INDEFINITE
)
.setAction(resources.getString(R.string._retry)) {
ensureBluetoothIsEnabled()
}
.show()
}
}
}
private val requestBluetoothPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
bluetoothEnableResultLauncher.launch(enableBluetoothIntent)
} else {
// Explain to the user that the feature is unavailable because the
// feature requires a permission that the user has denied. At the
// same time, respect the user's decision. Don't link to system
// settings in an effort to convince the user to change their
// decision.
Snackbar.make(
binding.root,
getString(R.string.without_bluetooth_you_cant_pair_with_your_mozi),
Snackbar.LENGTH_INDEFINITE
)
.setAction(resources.getString(R.string._retry)) {
ensureBluetoothIsEnabled()
}
.show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupViews()
ensureBluetoothIsEnabled()
}
private fun setupViews() {
//Here we setup the behavior of the button in our rationale dialog: basically we need to
// rerun the permissions check logic if it was already denied
binding.bluetoothPermissionsRationaleDialogButton.setOnClickListener {
binding.permissionsRationaleDialog.animateShow(false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
requestBluetoothPermissionLauncher.launch(Manifest.permission.BLUETOOTH_CONNECT)
} else {
requestBluetoothPermissionLauncher.launch(Manifest.permission.BLUETOOTH)
}
}
}
private fun ensureBluetoothIsEnabled() {
binding.loadingSpinner.show()
val bluetoothManager: BluetoothManager = getSystemService(BluetoothManager::class.java)
val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.adapter
if (bluetoothAdapter == null) {
// Device doesn't support Bluetooth
binding.loadingSpinner.hide()
Snackbar.make(
binding.root,
resources.getString(R.string.you_need_a_bluetooth_enabled_device),
Snackbar.LENGTH_INDEFINITE
).show()
}
if (bluetoothAdapter?.isEnabled == false) {
// Check if Bluetooth permissions have been granted before we try to enable the
// device
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.BLUETOOTH_CONNECT //TODO: test if this needs variant for legacy devices
) != PackageManager.PERMISSION_GRANTED
) {
/**
* We DON'T have Bluetooth permissions. We have to get them before we can ask the
* user to enable Bluetooth
*/
binding.loadingSpinner.hide()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_CONNECT)) {
binding.permissionsRationaleDialog.animateShow(true)
} else {
requestBluetoothPermissionLauncher.launch(Manifest.permission.BLUETOOTH_CONNECT)
}
} else {
if (shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH)) {
binding.permissionsRationaleDialog.animateShow(true)
} else {
requestBluetoothPermissionLauncher.launch(Manifest.permission.BLUETOOTH)
}
}
return
} else {
/**
* We DO have Bluetooth permissions. Now let's prompt the user to enable their
* Bluetooth radio
*/
binding.loadingSpinner.hide()
bluetoothEnableResultLauncher.launch(enableBluetoothIntent)
}
} else {
/**
* Bluetooth is enabled, we're good to continue with normal app flow
*/
binding.loadingSpinner.hide()
}
}
}
Android Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Bluetooth Permissions -->
<uses-feature android:name="android.software.companion_device_setup" android:required="true"/>
<uses-feature android:name="android.hardware.bluetooth" android:required="true"/>
<!-- Request legacy Bluetooth permissions on older devices. -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Needed only if your app looks for Bluetooth devices.
If your app doesn't use Bluetooth scan results to derive physical
location information, you can strongly assert that your app
doesn't derive physical location. -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags= "neverForLocation"
tools:targetApi="s" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
...
</manifest>
You could try using an empty BluetoothDeviceFilter like this:
private val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder().build()
to signal to the API that you want Bluetooth devices, and see if at least the phone sees your device.
Then you could try again with the name filter, this time adding a service UUID filter with BluetoothDeviceFilter.Builder.addServiceUuid.
If you don't know the UUID of your device or don't want to use it as a filter, you can use an arbitrary one and set the mask to all zeros (the docs suggest that it might also work using null values).
This is a hackish solution, but it might help you move a step further
It might be a permission issue.
In the docs, I read:
The BLUETOOTH_ADVERTISE, BLUETOOTH_CONNECT, and BLUETOOTH_SCAN permissions are runtime permissions. Therefore, you must explicitly request user approval in your app before you can look for Bluetooth devices, make a device discoverable to other devices, or communicate with already-paired Bluetooth devices.
So you could to add the following code in your HomeFragment class:
private val requestMultiplePermissions = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
permissions.entries.forEach {
Log.d("Permission Request", "${it.key} = ${it.value}")
}
}
private val requestBluetooth = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
// granted
} else {
// denied
}
}
and in the onCreateView method:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
requestMultiplePermissions.launch(arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT
))
} else {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
requestBluetooth.launch(enableBtIntent)
}
to request the permissions at runtime.
The documentation does not mention it, but it appears that even with the CompanionDeviceManager the location access must be enabled on the device.
The app does not need the location permission anymore, but it must be enabled.
I've been experiencing some odd behavior on Android 12. Specifically with a MediaBrowserService. The app will play sounds in the background for extended periods of time. I am running into two distinct but unwanted behaviors on Android 12.
On a pixel 5a - the notification appears, but will disappear after an hour or so in the background. The music is still playing fine and can be paused if the app is opened up.
On a Samsung s21 - the notification appears when the sound plays. but if it is stopped, and another started, the notification will not appear again upon starting the service until the phone itself is restarted.
On a s9 running Android 10, and a OnePlus 8 running Android 11, there are no issues.
Creating the notification seems pretty straightforward:
companion object {
const val ACTION_STOP = "action_stop"
const val CHANNEL_ID = "app_notification_channel"
const val NOTIFICATION_CHANNEL_TAG = "Playing Controls"
const val ONGOING_NOTIFICATION_ID = 2222
}
private fun refreshNotification(stopCalled: Boolean = false) {
val sounds = playingSounds
if (sounds.isEmpty() || stopCalled) {
// if we lost audio focus we will expect to restart momentarily. Don't stop the service
if (!audioFocusTakenBackground) {
Timber.e("Stopping Foreground")
stopForeground(true)
}
return
}
val builder = NotificationCompat.Builder(this, CHANNEL_ID).apply {
color = this#SoundPlayerService.getColor(R.color.magenta_0FF)
val nowPlaying = getString(R.string.action_now_playing)
setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
setContentTitle(nowPlaying)
setContentText(name)
setSmallIcon(R.drawable.ic_icon_small)
setContentIntent(relaunchPendingIntent())
setSilent(true)
setCategory(NotificationCompat.CATEGORY_SERVICE)
priority = NotificationCompat.PRIORITY_HIGH
addAction(NotificationCompat.Action(R.drawable.ic_playbar_stop, getString(R.string.action_stop), stopIntent))
setStyle(
androidx.media.app.NotificationCompat.MediaStyle()
.setShowActionsInCompactView(0)
.setMediaSession(mediaSession.sessionToken)
.setShowCancelButton(true)
.setCancelButtonIntent(stopIntent)
)
}
// If we lost audio focus in the background, we don't stop the service.
// Ensure that its not called in the background aka failure android 12
if (!audioFocusTakenBackground && Application.instance.isAppInForeground) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(
ONGOING_NOTIFICATION_ID,
builder.build(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
)
} else {
startForeground(
ONGOING_NOTIFICATION_ID,
builder.build()
)
}
}
}
The media session is created earlier:
// Create a MediaSession
mediaSession = MediaSessionCompat(this, this.javaClass.name).apply {
setSessionActivity(sessionActivityPendingActivity)
isActive = true
}
sessionToken = mediaSession.sessionToken
The media session is then hooked up to an ExoPlayer instance via a MediaSessionConnector
if (mediaSession != null) {
MediaSessionConnector(mediaSession).also { mediaSessionConnector ->
mediaSessionConnector.setPlayer(mCurrentPlayer)
}
}
Again, this hasn't been an issue until 12.
If anyone has seen this or could point me in a way to try and debug, I'd be extremely grateful.
Thanks!
In my driving-companion app, I have a need to detect the state of Android Auto. For several years now, I've been using UiModeManager to get the current state at startup and a BroadcastReceiver to detect state changes while the app is running. This has always worked perfectly, until Android 12. With Android 12, UiModeManager always reports UI_MODE_TYPE_NORMAL, even when Android Auto is connected and active, and my BroadcastReceiver is never called after connecting or disconnecting.
This is my code for detecting state at startup:
inCarMode = uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR;
and this is my BroadcastReceiver setup:
IntentFilter carModeFilter = new IntentFilter();
carModeFilter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE);
carModeFilter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE);
registerReceiver(carModeReceiver, carModeFilter);
Again, this has always worked perfectly with Android 5 through Android 11. Is this a bug in Android 12, or is there some new way to detect Android Auto state in Android 12?
You need to use the CarConnection API documented here
Configuration.UI_MODE_TYPE_CAR is not working on Anroid 12. As #Pierre-Olivier Dybman said, you can use CarConnection API in the androidx.car.app:app library. But that is too heavy to import entire library only for car connections if you don't need other features.
So I write a piece of code base on the CarConnection to detect Android Auto connection, as below:
class AutoConnectionDetector(val context: Context) {
companion object {
const val TAG = "AutoConnectionDetector"
// columnName for provider to query on connection status
const val CAR_CONNECTION_STATE = "CarConnectionState"
// auto app on your phone will send broadcast with this action when connection state changes
const val ACTION_CAR_CONNECTION_UPDATED = "androidx.car.app.connection.action.CAR_CONNECTION_UPDATED"
// phone is not connected to car
const val CONNECTION_TYPE_NOT_CONNECTED = 0
// phone is connected to Automotive OS
const val CONNECTION_TYPE_NATIVE = 1
// phone is connected to Android Auto
const val CONNECTION_TYPE_PROJECTION = 2
private const val QUERY_TOKEN = 42
private const val CAR_CONNECTION_AUTHORITY = "androidx.car.app.connection"
private val PROJECTION_HOST_URI = Uri.Builder().scheme("content").authority(CAR_CONNECTION_AUTHORITY).build()
}
private val carConnectionReceiver = CarConnectionBroadcastReceiver()
private val carConnectionQueryHandler = CarConnectionQueryHandler(context.contentResolver)
fun registerCarConnectionReceiver() {
context.registerReceiver(carConnectionReceiver, IntentFilter(ACTION_CAR_CONNECTION_UPDATED))
queryForState()
}
fun unRegisterCarConnectionReceiver() {
context.unregisterReceiver(carConnectionReceiver)
}
private fun queryForState() {
carConnectionQueryHandler.startQuery(
QUERY_TOKEN,
null,
PROJECTION_HOST_URI,
arrayOf(CAR_CONNECTION_STATE),
null,
null,
null
)
}
inner class CarConnectionBroadcastReceiver : BroadcastReceiver() {
// query for connection state every time the receiver receives the broadcast
override fun onReceive(context: Context?, intent: Intent?) {
queryForState()
}
}
internal class CarConnectionQueryHandler(resolver: ContentResolver?) : AsyncQueryHandler(resolver) {
// notify new queryed connection status when query complete
override fun onQueryComplete(token: Int, cookie: Any?, response: Cursor?) {
if (response == null) {
Log.w(TAG, "Null response from content provider when checking connection to the car, treating as disconnected")
notifyCarDisconnected()
return
}
val carConnectionTypeColumn = response.getColumnIndex(CAR_CONNECTION_STATE)
if (carConnectionTypeColumn < 0) {
Log.w(TAG, "Connection to car response is missing the connection type, treating as disconnected")
notifyCarDisconnected()
return
}
if (!response.moveToNext()) {
Log.w(TAG, "Connection to car response is empty, treating as disconnected")
notifyCarDisconnected()
return
}
val connectionState = response.getInt(carConnectionTypeColumn)
if (connectionState == CONNECTION_TYPE_NOT_CONNECTED) {
Log.i(TAG, "Android Auto disconnected")
notifyCarDisconnected()
} else {
Log.i(TAG, "Android Auto connected")
notifyCarConnected()
}
}
}
}
This solution works on android 6~12. If you need to detect car connection status on android 5, use the Configuration.UI_MODE_TYPE_CAR solution.
As we know Google has updated their policies that New apps will need to must target Android 11 (API level 30) or higher link. My app is working as expected if I am using the lower targeted SDK but When am I using targeted SDK 30 then It is not working as Expected.
There are the following main functionality in the App:
Available WiFi should be visible on list for Android 6 to 11 (with latest Android version)
After clicking on any list item user should connect with their respected WiFi. It can be OPEN or other.
After connected with respective WiFi, It will redirect to captive page if any. Captive page will be WebView into the app.
Now coming to the point, I am facing the following issue with the targeted 30 SDK.
When I am using the latest suggestion wifi connection code [1] then it's working partially.There is one issue, Suppose we are already connected to another WiFi connection and I am trying to connect with new WiFi then It's not connecting to new WiFi but I need to connect with the New Wifi.
Code which is using in the App with targeted SDK 30 (Which is not connecting with New Wifi)-
val suggestion = WifiNetworkSuggestion.Builder()
.setSsid(SSID) // SSID of network
.setWpa2Passphrase(wifiPassword) // password is network is not open
//.setIsAppInteractionRequired(true) // Optional (Needs location permission)
.build()
val suggestionsList = listOf(suggestion)
val wifiManager =
applicationContext.getSystemService(WIFI_SERVICE) as WifiManager
if (status != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
// do error handling here
Log.e("NETWORK", "Error")
}
// Optional (Wait for post connection broadcast to one of your suggestions)
val intentFilter =
IntentFilter(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);
val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.e("NETWORK", "broadcastReceiver")
if (!intent.action.equals(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)) {
return;
}
// do post connect processing here
Log.e("NETWORK", "post connect")
}
};
registerReceiver(broadcastReceiver, intentFilter)
Whenever my targeted SDK was 29 then I am using the WifiNetworkSpecifier [2] approach for connecting WiFi in Android 10 and 11. It was also working.
Code which is using in the App with targeted SDK 29 (Which is working as expected but targeted SDK must be 28 or 29 otherwise Internet will not work out of the Application)-
private fun android10andMoreVersionsWithoutOuterInternet(
scanResult: ScanResult,
wifiSSID: String,
wifiPassword: String,
capabilities: String
) {
// Android 10 (API level 29) -- Android Q (Android 10)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val wifiManager =
this.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiNetworkSpecifier = WifiNetworkSpecifier.Builder()
.setSsid(wifiSSID)
//.setSsidPattern(PatternMatcher(wifiSSID, PatternMatcher.PATTERN_PREFIX))
.setWpa2Passphrase(wifiPassword)
.build()
val networkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
//.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
//.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
//.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.setNetworkSpecifier(wifiNetworkSpecifier)
.build()
val connectivityManager =
this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
Log.d("NETWORK", "Network available")
super.onAvailable(network)
// To make sure that requests don't go over mobile data
connectivityManager.bindProcessToNetwork(network)
//unregister network callback
//connectivityManager.unregisterNetworkCallback(this)
// connectivityManager.bindProcessToNetwork(null)
gotoNextScreen(scanResult, wifiManager)
}
override fun onUnavailable() {
Log.d("NETWORK", "Network unavailable")
super.onUnavailable()
}
override fun onLosing(network: Network, maxMsToLive: Int) {
Log.d("NETWORK", "onLosing")
super.onLosing(network, maxMsToLive)
}
override fun onLost(network: Network) {
Log.d("NETWORK", "onLost")
super.onLost(network)
//connectivityManager.bindProcessToNetwork(null)
//connectivityManager.unregisterNetworkCallback(this)
}
}
connectivityManager.requestNetwork(networkRequest, networkCallback)
val builder = NetworkRequest.Builder()
builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
connectivityManager.registerNetworkCallback(builder.build(), networkCallback)
//connectivityManager.registerNetworkCallback(networkRequest, networkCallback) // For listen
}
}
Everything is working as expected with old depreciated code with Android 9..
Code which is using in Android 9 abd below (Which is working as expected and It is not impacting with any targeted SDK)-
private fun android9AndPreviousVersion(
scanResult: ScanResult,
wifiSSID: String,
wifiPassword: String,
capabilities: String
) {
val conf = WifiConfiguration()
conf.SSID =
"\"" + wifiSSID + "\"" // Please note the quotes. String should contain ssid in quotes
conf.status = WifiConfiguration.Status.ENABLED
conf.priority = 40
if (Common.checkWifiType(capabilities) == "WEP") {
Log.e("NETWORK", "Configuring WEP")
conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
conf.allowedProtocols.set(WifiConfiguration.Protocol.RSN)
conf.allowedProtocols.set(WifiConfiguration.Protocol.WPA)
conf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN)
conf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED)
conf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
conf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40)
conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104)
if (wifiPassword.matches(Regex("^[0-9a-fA-F]+$"))) {
conf.wepKeys[0] = wifiPassword
} else {
conf.wepKeys[0] = "\"" + wifiPassword + "\""
}
conf.wepTxKeyIndex = 0
} else if (Common.checkWifiType(capabilities) == "WPA") {
Log.e("NETWORK", "Configuring WPA")
conf.allowedProtocols.set(WifiConfiguration.Protocol.RSN)
conf.allowedProtocols.set(WifiConfiguration.Protocol.WPA)
conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK)
conf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
conf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40)
conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104)
conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP)
conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP)
conf.preSharedKey = "\"" + wifiPassword + "\""
} else {
Log.e("NETWORK", "Configuring OPEN network")
conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
}
val wifiManager =
this.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
val networkId = wifiManager.addNetwork(conf)
Log.e("NETWORK", "Add result $networkId")
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return
}
val list = wifiManager.configuredNetworks
for (i in list) {
if (i.SSID != null && i.SSID == "\"" + wifiSSID + "\"") {
Log.e("NETWORK", "WifiConfiguration SSID " + i.SSID)
val isDisconnected = wifiManager.disconnect()
Log.e("NETWORK", "isDisconnected : $isDisconnected")
val isEnabled = wifiManager.enableNetwork(i.networkId, true)
Log.e("NETWORK", "isEnabled : $isEnabled")
val isReconnected = wifiManager.reconnect()
Log.e("NETWORK", "isReconnected : $isReconnected")
break
}
}
//val connectionInfo: WifiInfo = wifiManager.getConnectionInfo()
gotoNextScreen(scanResult, wifiManager)
}
Conclude: When am I using WifiNetworkSpecifier for connecting to Available WiFi with targeted SDK 30 then I am able to connect but My Internet is only working in the App. When am I using latest Suggestion wifi for connecting to Available WiFi with targeted SDK 30 then I am unable to connect with the New WiFi. I am facing this issue in Android 10 and Android 11 devices.
Please suggest me for the solution. Please check my POC code [here]
The only way to have internet with wifi is :
In Android 10, you should implement wifiConfiguration to connect/disconnect
connect function :
val wifiConfiguration = WifiConfiguration()
wifiConfiguration.SSID = "\"${configuration.ssid}\""
wifiConfiguration.preSharedKey = "\"${configuration.password}\""
var netId = wifiManager.addNetwork(wifiConfiguration)
if (netId == -1) {
// If the network configuration with the same SSID already exists, we need to retrieve the configured networks and get the network id of the network corresponding to the given SSID.
netId = configuredNetworkForSSID(configuration.ssid)?.networkId ?: -1
}
wifiManager.enableNetwork(netId, true)
disconnect function :
wifiManager.removeNetwork(it.networkId)
wifiManager.disconnect()
In Android 11
You should use :
WifiSuggestion
final WifiNetworkSuggestion suggestion1 =
new WifiNetworkSuggestion.Builder()
.setSsid("test111111")
.setIsAppInteractionRequired(true) // Optional (Needs location permission)
.build();
final IntentFilter intentFilter =
new IntentFilter(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);
final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals(
WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)) {
return;
}
// do post connect processing here...
}
};
context.registerReceiver(broadcastReceiver, intentFilter);
to disconnect from Android 11, it's really weird, Google use a stupid solution witch: you should leave the place, if you try to disconnect manually by deleting the wifi from settings, then you can't re-connect automatically. (You should add the wifi network manually to recover the auto-connect behaviour)
Wifi Connection in Android 10 : the OS present a small push-notification to accept the connection, if you didn't add a pop-up to alert the user, he will be missed it;
in Android 11, Google changed the notification with an alertBox to alert the user.
if you use WifiNetworkSpecifier or another API you will have a wifi connection but without internet.
To restrict the control of third party app, Android has changed the way of connecting to wifi network in Android 10 and Android 11
If you look closely to your first solution, you can see that you are suggesting the system that these wifi are available to connect, now the system will decide whether to connect or not. Now here if system is already connection to some wifi which have active internet connection then Android will ignore your suggestion until that wifi is unavailable.
Right, if you use WifiNetworkSpecifier it restrict the uses of the network to your app only.
I am doing a scan for a device with a specific service UUID, the function scan, the device is found, and I connect.
I connect to the device every 2 minutes for 20 seconds.
Everything is working.
After a while the phone no longer connects to the device.
The device is found but unnamed.
I have to turn off the bluetooth and turn it on manually
Then it works again.
When I turn off and turn on the bluetooth programaticcally it doesn't work.
I guess it's a cache problem? ...
private fun startScan() {
if (isScanning) return
log("Start scanning for ${searchedDevice?.uuid.toString()}...")
val builder: ScanFilter.Builder = ScanFilter.Builder()
searchedDevice?.let {
builder.setServiceUuid(ParcelUuid.fromString(it.uuid.toString()))
}
val filter: ScanFilter = builder.build()
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setReportDelay(200)
.build()
isScanning = true
bluetoothLeScanner.startScan(listOf(filter), settings, scanCallback)
}
onBacthScan:
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
super.onBatchScanResults(results)
results?.firstOrNull()?.let {
connectToPeripheral(it.device, connectionCompletion)
}
}
}
OnConnection:
override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
super.onConnectionStateChange(gatt, status, newState)
I get status 133 what is?
EDIT:
Error 133 is:
case 0x0085:
return "GATT ERROR"; // Device not reachable
How to solve this error programaticcally