I'm trying to add vibration effects to my Android game. I found some code that seems to work, but it's deprecated. What's the current way to create and deploy a vibrator?
var vibrator:Vibrator = getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
vibrator.vibrate(createPredefined(EFFECT_CLICK))
}else{
vibrator.vibrate(50)
}
The parts that are showing up as deprecated are "VIBRATOR-SERVICE" and vibrate.vibrate(50).
Step 1 : Add permission to the AndroidMenifest.xml
<uses-permission android:name="android.permission.VIBRATE" />
Step 2 : You can use this function for vibration.
if (ctx != null) {
if (Build.VERSION.SDK_INT >= 31) {
val vibratorManager =
ctx.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
val vibrator = vibratorManager.defaultVibrator
vibrator.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
} else {
val v = ctx.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
if (Build.VERSION.SDK_INT >= 26) {
v.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
} else {
v.vibrate(200L)
}
}
}
I'm developing an app in which I want to continuously scan for Ble advertisement packets, even if the user locks the screen. With my current implementation this works fine with Android 10, but with Android 11 it stops once the user locks the screen. For scanning Ble packets I first request a few permissions, namely:
coarse and fine location
bluetooth scan
access background location
I start a simple foreground service (also added foreground service permission to my Manifest) with:
private fun startBleService() {
serviceIntent = Intent(baseContext, ScanService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Log.i("Background Act", "Starting foreground service for android 8.0+")
applicationContext.startForegroundService(serviceIntent)
} else {
Log.i("Background Act", "Starting foreground service for versions < android 8.0")
applicationContext.startService(serviceIntent)
}
}
This will call startForeground(notificationID, notification) in the onStartCommand function of my ScanService, thus requesting to run in foreground. After this I start the actual Ble scan functionalities. I also added android:foregroundServiceType="location" to the service in the Manifest.
My ScanService Code:
class ScanService : Service() {
private val channelID = "CustomChannelID"
private val notificationID = 7
private lateinit var bluetoothManager: BluetoothManager
private lateinit var bluetoothAdapter: BluetoothAdapter
private lateinit var bluetoothLeScanner: BluetoothLeScanner
private var scanCounter = 0
private var _bleSingleScanResults = HashMap<String, MutableList<String>>()
// all scans combined in an array, internally used
private var _bleAllScanResults = arrayListOf<HashMap<String, MutableList<String>>>()
// only starts a new scan if its not already scanning
private var scanning = false
private val notificationManager by lazy {getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager}
#RequiresApi(Build.VERSION_CODES.M)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i("OnStartCommand Service", "Is started")
val notification: Notification =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel(notificationManager)
Notification.Builder(this, channelID)
.setContentTitle("Content Title")
.setContentText("Content Text")
.setTicker("Ticker")
.build()
} else {
NotificationCompat.Builder(this, channelID)
.setContentTitle("BLE Scanning Service")
.setContentText("Scanning BLE in the background")
.setTicker("Ticker")
.build()
}
// use custom non-zero notification ID
startForeground(notificationID, notification)
//TODO: start in new thread
scanBle()
// If we get killed, after returning from here, restart, recreating notification again though
return START_STICKY
}
#RequiresApi(Build.VERSION_CODES.M)
fun scanBle() {
bluetoothManager = this.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = bluetoothManager.adapter
if (bluetoothAdapter.bluetoothLeScanner == null) {
Log.d("BLE", "Device doesn't support BLE")
Toast.makeText(
this,
"It seems like your device does not support BLE. This is a crucial part of this app. \n " +
"Unfortunately you can't contribute to the dataset of scanned locations.",
Toast.LENGTH_LONG
).show()
return
}
bluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner
runBLEScan()
}
#SuppressLint("MissingPermission") //since we check beforehand in the MainActivity for permissions already
private fun runBLEScan() {
val scanSettings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
val scanFilters = listOf(ScanFilter.Builder().build())
if (!scanning) {
scanning = true
Log.i("BLE", "--- STARTING BLE SCAN ---")
bluetoothLeScanner.startScan(scanFilters, scanSettings, scanCallBackLe)
} else Log.d("BLE Scan", "Called scanning function but is currently already scanning!")
}
// ALWAYS ON UI-THREAD
private val scanCallBackLe = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
super.onScanResult(callbackType, result)
// discard result if payload is null
if(result.scanRecord == null || result.scanRecord!!.bytes == null) {
return
}
println("Payload: ${result.scanRecord?.bytes?.toHexString()}")
// check if device already discovered in a scan, if so increase counter, else make new
// entry in the result HashMap
if (_bleSingleScanResults.isEmpty() || !_bleSingleScanResults.containsKey(result.device.toString())) {
// device wasn't seen before
_bleSingleScanResults[result.device.toString()] =
mutableListOf(result.rssi.toString(), result.scanRecord!!.bytes.toHexString(), "1")
} else {
// update already existing entry
val cntr = _bleSingleScanResults[result.device.toString()]!![2].toInt() + 1
_bleSingleScanResults[result.device.toString()]!![2] = cntr.toString()
}
}
override fun onScanFailed(errorCode: Int) {
super.onScanFailed(errorCode)
Log.d("BLE ScanResult", "Scan failed code: $errorCode")
}
}
private fun ByteArray.toHexString() = joinToString("", "[0x", "]") { "%02X".format(it) }
private fun createChannel(notificationManager: NotificationManager) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return
}
val channel =
NotificationChannel(channelID, "Scan Service", NotificationManager.IMPORTANCE_DEFAULT)
channel.description = "Hello! This is a notification."
notificationManager.createNotificationChannel(channel)
}
#SuppressLint("MissingPermission")
override fun onDestroy() {
Log.d("Destroyed Service", "That's even worse")
bluetoothLeScanner.stopScan(scanCallBackLe)
super.onDestroy()
}
#SuppressLint("MissingPermission")
override fun stopService(name: Intent?): Boolean {
Log.d("Stopped Service", "That's bad")
bluetoothLeScanner.stopScan(scanCallBackLe)
stopSelf()
return super.stopService(name)
}
override fun onBind(intent: Intent): IBinder {
TODO("Return the communication channel to the service.")
}
Parts of my Manifest:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<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" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
...
<service
android:name=".ScanService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="location" />
...
</application>
This works fine with Android 10 (tested on a Huawei device), but unfortunately not on Android 11 (Samsung A22). Is there any other permission I need to be able to keep scanning even if the user locks the screen in Android 11?
As the title says, i upgraded to API 31. I had a function to perform a vibration, but in the line
val vib = this.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
the VIBRATOR_SERVICE is now shown as deprecated. How can i replace it? Or at least, what's the modern solution for API 31 and above?
EDIT: as Joachim Sauer wrote, the alternative is VibrationManager. What i need now is the equivalent line of code using VibrationManager.
val vib = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val vibratorManager =
getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibratorManager.defaultVibrator
} else {
#Suppress("DEPRECATION")
getSystemService(VIBRATOR_SERVICE) as Vibrator
}
The docs for this field say this:
This constant was deprecated in API level 31.
Use VibratorManager to retrieve the default system vibrator.
The most direct translation of code needing a Vibrator instance would be this:
val vibratorManager = this.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
val vibrator = vibratorManager.getDefaultVibrator();
Generally speaking whenever a class/method/field is deprecated like this then you should first check the documentation. Almost every single time it will tell you what to use instead (or in some cases that it has no replacement).
This code works for both old and new android devices. Reference to the docs Vibrate constantly for the specified period of time.. You should use a VibrationEffect instead to create the vibration pattern.
In Java:
Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
final int DELAY = 0, VIBRATE = 1000, SLEEP = 1000, START = 0;
long[] vibratePattern = {DELAY, VIBRATE, SLEEP};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createWaveform(vibratePattern, START));
} else {
// backward compatibility for Android API < 26
// noinspection deprecation
vibrator.vibrate(vibratePattern, START);
}
In Kotlin:
val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
val DELAY = 0
val VIBRATE = 1000
val SLEEP = 1000
val START = 0
val vibratePattern = longArrayOf(DELAY.toLong(), VIBRATE.toLong(), SLEEP.toLong())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createWaveform(vibratePattern, START))
} else {
// backward compatibility for Android API < 26
// noinspection deprecation
vibrator.vibrate(vibratePattern, START)
}
Edit
This method works for API level 30 below properly, so to completely use this on API level 31 above you need to use VIBRATOR_MANAGER_SERVICE instead of VIBRATOR_SERVICE, to retrieve the default vibrator service.
The correct code is below (in Java) :
Vibrator vibrator;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
VibratorManager vibratorManager = (VibratorManager) getSystemService(Context.VIBRATOR_MANAGER_SERVICE);
vibrator = vibratorManager.getDefaultVibrator();
} else {
// backward compatibility for Android API < 31,
// VibratorManager was only added on API level 31 release.
// noinspection deprecation
vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
}
final int DELAY = 0, VIBRATE = 1000, SLEEP = 1000, START = 0;
long[] vibratePattern = {DELAY, VIBRATE, SLEEP};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createWaveform(vibratePattern, START));
} else {
// backward compatibility for Android API < 26
// noinspection deprecation
vibrator.vibrate(vibratePattern, START);
}
The correct code is below (in Kotlin) :
val vibrator: Vibrator
vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val vibratorManager: VibratorManager = getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibratorManager.getDefaultVibrator()
} else {
// backward compatibility for Android API < 31,
// VibratorManager was only added on API level 31 release.
// noinspection deprecation
getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
}
val DELAY = 0
val VIBRATE = 1000
val SLEEP = 1000
val START = 0
val vibratePattern = longArrayOf(DELAY.toLong(), VIBRATE.toLong(), SLEEP.toLong())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createWaveform(vibratePattern, START))
} else {
// backward compatibility for Android API < 26
// noinspection deprecation
vibrator.vibrate(vibratePattern, START)
}
Pulled together the various answers and cleaned them up to take into account changes in SDK 31 and 26, while providing backward compatibility.
#SuppressWarnings("deprecation")
private void vibrate() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
VibratorManager vibratorManager = (VibratorManager) getContext().getSystemService(Context.VIBRATOR_MANAGER_SERVICE);
Vibrator vibrator = vibratorManager.getDefaultVibrator();
vibrator.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE));
}
else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
Vibrator vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
// API < 26
Vibrator vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(500);
}
}
Handle SDK < 26, 26..32 and >= 33
private val vibrator: Vibrator by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
(getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager).defaultVibrator
} else {
#Suppress("DEPRECATION")
getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
}
}
#SuppressLint("MissingPermission")
private fun startVibrator() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
vibrator.vibrate(
VibrationEffect.createOneShot(1000, VibrationEffect.DEFAULT_AMPLITUDE),
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ALARM)
)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
#Suppress("DEPRECATION")
vibrator.vibrate(
VibrationEffect.createOneShot(1000, VibrationEffect.DEFAULT_AMPLITUDE),
AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ALARM)
.build()
)
} else {
#Suppress("DEPRECATION")
vibrator.vibrate(1000)
}
}
I created a wrapper class to handle the compatibility issue:
class VibratorHelper private constructor(private val context: Context) {
#Suppress("DEPRECATION")
fun vibrate(duration: Long) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibratorManager.defaultVibrator.run {
cancel()
vibrate(VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE))
}
} else {
val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
vibrator.cancel()
if (Build.VERSION.SDK_INT >= 26) {
vibrator.vibrate(VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE))
} else {
vibrator.vibrate(duration)
}
}
}
companion object {
#JvmStatic
fun from(context: Context): VibratorHelper? {
val hasVibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibratorManager.defaultVibrator.hasVibrator()
} else {
#Suppress("DEPRECATION")
val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
vibrator.hasVibrator()
}
return if (hasVibrator) VibratorHelper(context.applicationContext) else null
}
}
}
Here's how to use it:
val vibrator = VibratorHelper.from(context)
vibrator?.vibrate(500)
this is simple answer for both old and new api
Give permission for vibration
<uses-permission android:name="android.permission.VIBRATE" />
After that use this code for kotlin
#Suppress("DEPRECATION")
private fun vibrate(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val vibratorManager = getSystemService(VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibratorManager.defaultVibrator
} else {
val vibrator = getSystemService(VIBRATOR_SERVICE) as Vibrator
vibrator.vibrate(10)
}
}
after that just call the method
This is what I use in my app (Kotlin). It handles all the old versions and hides the deprecated warnings. It does one short vibrate.
fun AppCompatActivity.vibrate() {
val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val vibratorManager = getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibratorManager.defaultVibrator
} else {
#Suppress("DEPRECATION")
getSystemService(AppCompatActivity.VIBRATOR_SERVICE) as Vibrator
}
val duration = 200L
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE))
} else {
#Suppress("DEPRECATION")
vibrator.vibrate(duration)
}
}
There are tutorials for this on the internet but the code is in Java and I'm new to Kotlin so I am not able to figure out how I can do the same in Kotlin.
The code I tried in Kotlin.
var pattern = longArrayOf(0, 200, 500)
val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
vibrator.vibrate(pattern, 5)
Edit 2:
I created a function
#RequiresApi(Build.VERSION_CODES.O)
private fun vibrate(context: Context){
var pattern = longArrayOf(0, 200, 500)
val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
vibrator.vibrate(VibrationEffect.createWaveform(pattern,5))
}
and i called it in the onFinish function of CountDownTimer
#RequiresApi(Build.VERSION_CODES.O)
override fun onFinish() {
vibrate(this#MeditateActivity)
}
i tried running the app on my android device but my device didn't vibrate at all
I thought I only have to insert <uses-permission android:name="android.permission.VIBRATE" /> to AndroidManifest.xml and a function with that content:
context?.getSystemService(Context.VIBRATOR_SERVICE).vibrate(30)
Reference
private fun Fragment.vibratePhone() {
val vibrator = context?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
if (Build.VERSION.SDK_INT >= 26) {
vibrator.vibrate(VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE))
} else {
vibrator.vibrate(200)
}
}