teclas - curso android desarrollo de aplicaciones móviles pdf
¿Escucha los botones de volumen en el servicio de fondo? (9)
Esto requiere Lollipop (v5.0 / API 21) o superior
Mi objetivo era ajustar el volumen del sistema desde un Servicio. Sin embargo, cualquier acción puede tomarse en la prensa.
public class VolumeKeyController {
private MediaSessionCompat mMediaSession;
private final Context mContext;
public VolumeKeyController(Context context) {
mContext = context;
}
private void createMediaSession() {
mMediaSession = new MediaSessionCompat(mContext, KeyUtil.log);
mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mMediaSession.setPlaybackState(new Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 0)
.build());
mMediaSession.setPlaybackToRemote(getVolumeProvider());
mMediaSession.setActive(true);
}
private VolumeProviderCompat getVolumeProvider() {
final AudioManager audio = mContext.getSystemService(Context.AUDIO_SERVICE);
int STREAM_TYPE = AudioManager.STREAM_MUSIC;
int currentVolume = audio.getStreamVolume(STREAM_TYPE);
int maxVolume = audio.getStreamMaxVolume(STREAM_TYPE);
final int VOLUME_UP = 1;
final int VOLUME_DOWN = -1;
return new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, maxVolume, currentVolume) {
@Override
public void onAdjustVolume(int direction) {
// Up = 1, Down = -1, Release = 0
// Replace with your action, if you don''t want to adjust system volume
if (direction == VOLUME_UP) {
audio.adjustStreamVolume(STREAM_TYPE,
AudioManager.ADJUST_RAISE, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
}
else if (direction == VOLUME_DOWN) {
audio.adjustStreamVolume(STREAM_TYPE,
AudioManager.ADJUST_LOWER, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
}
setCurrentVolume(audio.getStreamVolume(STREAM_TYPE));
}
};
}
// Call when control needed, add a call to constructor if needed immediately
public void setActive(boolean active) {
if (mMediaSession != null) {
mMediaSession.setActive(active);
return;
}
createMediaSession();
}
// Call from Service''s onDestroy method
public void destroy() {
if (mMediaSession != null) {
mMediaSession.release();
}
}
}
Sé cómo escuchar los botones de volumen en una actividad. ¿Pero puedo hacer eso en un servicio en segundo plano? Si es así, ¿cómo hacer eso?
A juzgar por el par de otras preguntas sobre este tema, no.
Otra pregunta 1 , Otra pregunta 2
Los servicios simplemente no reciben devoluciones de llamada de KeyEvent.
Android no documenta las API al interactuar con los botones de volumen en ese caso. Así que supongo que la respuesta es no ...
La aplicación AOSP Music tiene un Servicio ( MediaPlaybackService ) que responde a eventos de clave de volumen registrando un BroadcastReceiver ( MediaButtonIntentReceiver ).
Aquí está el fragmento de código donde registra el receptor:
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
ComponentName rec = new ComponentName(getPackageName(),
MediaButtonIntentReceiver.class.getName());
mAudioManager.registerMediaButtonEventReceiver(rec);
Además, no te olvides de manifiesto:
<receiver android:name="com.android.music.MediaButtonIntentReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
Esto funciona incluso si la aplicación de Música no está en primer plano. ¿No es eso lo que quieres?
Lamento decírtelo, pero ES POSIBLE. Use el siguiente código (PARA NUEVOS ANDROIDES, ESP. MARSHMALLOW, VEA LA PARTE INFERIOR DE LA RESPUESTA):
public class SettingsContentObserver extends ContentObserver {
int previousVolume;
Context context;
public SettingsContentObserver(Context c, Handler handler) {
super(handler);
context=c;
AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
previousVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC);
}
@Override
public boolean deliverSelfNotifications() {
return super.deliverSelfNotifications();
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
int currentVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC);
int delta=previousVolume-currentVolume;
if(delta>0)
{
Logger.d("Ściszył!");
previousVolume=currentVolume;
}
else if(delta<0)
{
Logger.d("Zrobił głośniej!");
previousVolume=currentVolume;
}
}
}
Luego, en su servicio en Crear, regístrelo con:
mSettingsContentObserver = new SettingsContentObserver(this,new Handler());
getApplicationContext().getContentResolver().registerContentObserver(android.provider.Settings.System.CONTENT_URI, true, mSettingsContentObserver );
Luego anule el registro en onDestroy:
getApplicationContext().getContentResolver().unregisterContentObserver(mSettingsContentObserver);
Tenga en cuenta que este ejemplo juzga por cambio de volumen de medios, si desea usar otro volumen, cámbielo.
ACTUALIZAR:
El método supuestamente supuestamente no funciona en Marshmallow, PERO ahora hay una forma mucho mejor desde que se introdujo MediaSession. Entonces, primero debe migrar su código al patrón MediaController / MediaSession y luego usar este código:
private VolumeProviderCompat myVolumeProvider = null;
myVolumeProvider = new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, maxVolume, currentVolume) {
@Override
public void onAdjustVolume(int direction) {
// <0 volume down
// >0 volume up
}
};
mSession.setPlaybackToRemote(myVolumeProvider);
De alguna manera, las pulsaciones de los botones de volumen se detectan incluso con la pantalla apagada (¡solo asegúrese de registrar el receptor de intento de botón de medios adecuado si corresponde para su plataforma!)
ACTUALIZAR 2 ya que GalDude solicitó más información sobre cómo obtener MediaSession / MediaController. Lo siento, pero desde que dejé de usar Java, estará en Kotlin:
lateinit var mediaSession: MediaSessionCompat // you have to initialize it in your onCreate method
val kontroler: MediaControllerCompat
get() = mediaSession.controller // in Java it''s just getController() on mediaSession
// in your onCreate/start method:
mediaSession = MediaSessionCompat(this, "YourPlayerName", receiver, null)
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
mediaSession.isActive = true
if (ratingIsWorking) // note: rating crashes on some machines you have to check it!
mediaSession.setRatingType(RatingCompat.RATING_5_STARS)
mediaSession.setCallback(object : MediaSessionCompat.Callback() {
...
// here you have to implement what happens with your player when play/pause/stop/ffw etc. is requested - see exaples elsewhere
})
// onDestroy/exit method:
mediaSession.isActive = false
mediaSession.release()
Necesita reproducir un sonido en blanco del servicio, entonces solo usted puede escuchar los cambios de volumen. Lo siguiente funcionó para mí
Pasos
1. Coloque blank.mp3 en la carpeta sin formato (Descargar desde here )
2. Inicie los medios en onStartCommand ()
private MediaPlayer mediaPlayer;
public MyService() {
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
........
mediaPlayer = MediaPlayer.create(this, R.raw.blank);
mediaPlayer.setLooping(true);
mediaPlayer.start();
.......
return START_STICKY;
}
3. Debe elegir parar y lanzar Media Player. Es mejor hacerlo en onDestroy ()
@Override
public void onDestroy() {
mediaPlayer.stop();
mediaPlayer.release();
super.onDestroy();
}
4. Cree el receptor de difusión que escuchará los cambios de volumen
int volumePrev = 0;
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if ("android.media.VOLUME_CHANGED_ACTION".equals(intent.getAction())) {
int volume = intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_VALUE",0);
Log.i(TAG, "volume = " + volume);
if (volumePrev < volume) {
Log.i(TAG, "You have pressed volume up button");
} else {
Log.i(TAG, "You have pressed volume down button");
}
volumePrev = volume;
}
}
};
5. Registre el receptor de difusión en onStartCommand ()
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
.....
IntentFilter filter = new IntentFilter();
filter.addAction("android.media.VOLUME_CHANGED_ACTION");
registerReceiver(broadcastReceiver, filter);
....
return START_STICKY;
}
6. Eliminar el registro del receptor de broadccast en onDestroy ()
@Override
public void onDestroy() {
.....
unregisterReceiver(broadcastReceiver);
.....
super.onDestroy();
}
Eso es todo
Pude hacer que funcionara en dispositivos Android 5+ usando MediaSession
. Sin embargo, ContentObserver
sugerido por @ssuukk no funcionó para mí en los dispositivos 4.4 y 7.0 (al menos en las ROM en las que he estado probando). Aquí hay un ejemplo completo que funciona en Android 5+.
Servicio:
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.v4.media.VolumeProviderCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
public class PlayerService extends Service {
private MediaSessionCompat mediaSession;
@Override
public void onCreate() {
super.onCreate();
mediaSession = new MediaSessionCompat(this, "PlayerService");
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 0) //you simulate a player which plays something.
.build());
//this will only work on Lollipop and up, see https://code.google.com/p/android/issues/detail?id=224134
VolumeProviderCompat myVolumeProvider =
new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, /*max volume*/100, /*initial volume level*/50) {
@Override
public void onAdjustVolume(int direction) {
/*
-1 -- volume down
1 -- volume up
0 -- volume button released
*/
}
};
mediaSession.setPlaybackToRemote(myVolumeProvider);
mediaSession.setActive(true);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
mediaSession.release();
}
}
En AndroidManifest.xml
:
<application ...>
...
<service android:name=".PlayerService"/>
</application>
En tu actividad:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
startService(new Intent(this, PlayerService.class));
}
Hay varias cosas a tener en cuenta:
- Intercepta los botones de volumen por completo, por lo que mientras este código esté en ejecución, no podrá ajustar el volumen del timbre usando los botones de volumen. Esto podría ser posible solucionar, simplemente no lo intenté.
- Si ejecuta el ejemplo tal como está, los botones de volumen permanecerán controlados por la aplicación incluso cuando la pantalla esté apagada y la aplicación haya sido eliminada de la lista de "Aplicaciones recientes". Tendrás que ir a
Settings->Applications
, encontrar la aplicación y forzar detenerla para recuperar los botones de volumen.
checkout Controlar el volumen y la reproducción de su aplicación ... Esto ayudará a resolver su problema ... es posible que varias aplicaciones quieran escuchar presionar un botón desde el fondo, esta puede ser la razón por la cual KeyEvents solo puede ser manejado por Activities ya que son la interfaz al usuario presionando las teclas.
Nota: esto solo funciona para Actividades, y no Servicios como lo indica la pregunta.
Dependiendo del contexto en el que se requiera la devolución de llamada, una solución alternativa podría estar disponible.
Para poder detectar el botón de volumen, una Actividad debería anular la función dispatchKeyEvent
. Para que esto esté presente en actividades múltiples podría escribir una superclase que contenga la función anulada que se extiende por todas las actividades posteriores.
Aquí está el código para detectar las teclas de aumento / descenso de volumen:
// Over-ride this function to define what should happen when keys are pressed (e.g. Home button, Back button, etc.)
@Override
public boolean dispatchKeyEvent(KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_DOWN)
{
switch (event.getKeyCode())
{
case KeyEvent.KEYCODE_VOLUME_UP:
// Volume up key detected
// Do something
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
// Volume down key detected
// Do something
return true;
}
}
return super.dispatchKeyEvent(event);
}