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)) } } } }