Vibreur
Utilisation de l'API
- Permission requise : android.permission.VIBRATE
- Vibrator v = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE)
-
Méthodes utiles :
- boolean hasVibrator() : y-a-t-il un vibreur disponible ?
- vibrate(int durationInMillis) : vibre pendant la durée indiquée
-
vibrate(int[] array, int index) : vibre selon la séquence indiquée,
- array[0] indique un temps de non-vibration en millisecondes
- array[1] un temps de vibration
- ...
- et index spécifie un indice pour commencer la répétition
- cancel() : annule une vibration demandée
- Introduction à partir de l'API 26 de VibrationEffect qui permet d'agir sur l'amplitude de la vibration et de réaliser des retours haptiques
Exemple : un service de vibration
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License
package fr.upem.coursand.morse
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.IBinder
import android.os.Vibrator
import android.widget.Toast
import androidx.core.content.ContextCompat.getSystemService
/** A service that execute vibration sequences */
class VibratorService : Service() {
companion object {
val VIBRATION_ACTION = javaClass.name + ".vibrate"
/** Test if the vibrator is available on the current device */
fun isVibratorAvailable(context: Context) =
(context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator).let { it.hasVibrator() }
}
lateinit var vibrator: Vibrator
lateinit var handler: Handler
val vibrationEnder = Runnable { vibrator.cancel() }
override fun onCreate() {
super.onCreate()
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
handler = Handler() // create an handler to control the end of vibration
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent?.action == VIBRATION_ACTION) {
handler.removeCallbacks(vibrationEnder) // remove the planified end of previous vibration pttern
vibrationEnder.run() // cancel now the possible current vibration pattern
val pattern = intent.getLongArrayExtra("pattern")
val duration = intent.getLongExtra("duration", -1) // duration in millis, -1 if infinite
// execute the vibration pattern
if (duration >= 0)
Toast.makeText(this, "Start vibrator service for ${duration} seconds", Toast.LENGTH_SHORT).show()
vibrator.vibrate(pattern, 0)
if (duration != -1L) // schedule the end of vibration pattern with the handler
handler.postDelayed(vibrationEnder, duration)
}
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacks(vibrationEnder)
vibrationEnder.run()
}
override fun onBind(intent: Intent): IBinder {
throw NotImplementedError()
}
}
LED de signalisation
- LED de façade activée par l'usage de notifications
- Pas d'API pour le contrôle direct de la LED de notification (active par l'ajout d'une notification)
LED torche
- Utilisation de la LED présente au dos de l'appareil pour éclairage avec l'API Camera
- Requiert l'utilisation de la permission android.permission.FLASHLIGHT (et également CAMERA sur certains appareils)
Exemple : un service de séquence d'illumination (s'utilisant sur le même modèle que le service de vibration)
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License
package fr.upem.coursand.morse
import android.Manifest
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.hardware.Camera
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.os.Build
import android.os.IBinder
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import android.util.Log
import android.widget.Toast
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Future
class TorchService : Service() {
companion object {
val START_TORCH_ACTION = javaClass.name + ".startTorch"
/** test if the torch is available on the current device and if the camera permission is granted */
fun isTorchAvailable(context: Context) =
context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH) &&
ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
}
var camera: Camera? = null
lateinit var executor: ExecutorService
var currentTorchTask: Future<*>? = null
override fun onCreate() {
super.onCreate()
executor = Executors.newSingleThreadExecutor() // create a thread executor to control the torch
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent?.action == START_TORCH_ACTION) {
val pattern = intent.getLongArrayExtra("pattern")!!
val duration = intent.getLongExtra("duration", -1) // duration in millis, -1 if infinite
if (duration >= 0)
Toast.makeText(this, "Start torch service for ${duration} seconds", Toast.LENGTH_SHORT).show()
currentTorchTask?.cancel(true) // cancel the current torch task if existing
currentTorchTask = executor.submit {
Log.i(javaClass.name, "Starting torch with pattern ${pattern}")
try {
var index = 0
while (!Thread.interrupted()) {
when (index % 2) {
0 -> Thread.sleep(pattern[index])
1 -> activateTorch(pattern[index])
}
index = (index + 1) % pattern.size
}
} finally {
closeTorch()
}
}
}
return START_NOT_STICKY
}
private fun activateTorch(duration: Long) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
activateTorch1(duration)
else
activateTorch2(duration)
}
/** Oldest way to activate torch for API < M */
private fun activateTorch1(duration: Long) {
if (camera == null) camera = Camera.open()
Log.i(javaClass.name, "Camera open")
if (duration > 0L)
camera?.apply {
val params = parameters
params.flashMode = Camera.Parameters.FLASH_MODE_TORCH
parameters = params
startPreview()
Log.i(javaClass.name, "Activating torch")
try {
Thread.sleep(duration)
val params2 = parameters
params.flashMode = Camera.Parameters.FLASH_MODE_OFF
parameters = params2
} finally {
stopPreview() // executed even if there is an InterruptedException
Log.i(javaClass.name, "Disabling torch")
}
}
}
/** New way to activate the torch for API >= M */
@RequiresApi(Build.VERSION_CODES.M)
private fun activateTorch2(duration: Long) {
if (duration > 0L) {
val cm = getSystemService(Context.CAMERA_SERVICE) as CameraManager
if (cm != null) {
val cameraId = cm.cameraIdList.find { cm.getCameraCharacteristics(it)[CameraCharacteristics.FLASH_INFO_AVAILABLE] == true }
if (cameraId != null) {
cm.setTorchMode(cameraId, true)
try {
Thread.sleep(duration)
} finally {
cm.setTorchMode(cameraId, false)
}
}
}
}
}
private fun closeTorch() {
camera?.release()
camera = null
}
override fun onDestroy() {
super.onDestroy()
executor.shutdownNow() // stop the running thread for the torch
}
override fun onBind(intent: Intent): IBinder {
TODO("Return the communication channel to the service.")
}
}
Exemple : activité d'émission de messages en Morse par vibrations ou LED
Activité s'appuyant sur les services précédemment écrits (VibrationService et TorchService)
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License
package fr.upem.coursand.morse
import android.Manifest
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import fr.upem.coursand.R
import fr.upem.coursand.launcher.ActivityMetadata
import kotlinx.android.synthetic.main.activity_morse.*
@ActivityMetadata(permissions= [Manifest.permission.CAMERA, "android.permission.FLASHLIGHT"])
class MorseActivity : AppCompatActivity() {
val TIME_QUANTUM = 100L // in millis
private fun createIntent(vibrator: Boolean) =
Intent(this, if (vibrator) VibratorService::class.java else TorchService::class.java)
// Create the Intent used to start the service
private fun createStartIntent(): Intent? {
val intent = when {
vibratorView.isChecked -> createIntent(true).setAction(VibratorService.VIBRATION_ACTION)
torchView.isChecked -> createIntent(false).setAction(TorchService.START_TORCH_ACTION)
else -> null
}
val pattern = messageView.text.toString().toMorsePattern(TIME_QUANTUM)
intent?.putExtra("pattern", pattern)
intent?.putExtra("duration", durationView.text.toString().toLongOrNull()?.times(1000L) ?: -1L) // in millis
return intent
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_morse)
vibratorView.isEnabled = VibratorService.isVibratorAvailable(this)
torchView.isEnabled = TorchService.isTorchAvailable(this)
// start the service for vibration or torch (according to the checked radio button)
startButton.setOnClickListener {
val intent = createStartIntent()
if (intent != null) startService(intent) }
// stop all the services
// calling stopService on a non-started service does nothing
stopButton.setOnClickListener { listOf(false, true).forEach { stopService(createIntent(it)) } }
}
}