AudioPlayer: disable screen and switch to earpiece when holding phone to ear

Based of initial worked by @harshitbansal05
This commit is contained in:
Daniel Gultsch 2018-09-01 18:23:04 +02:00
parent f533e526fb
commit 06f37b0d2b
4 changed files with 149 additions and 8 deletions

View file

@ -0,0 +1,16 @@
package eu.siacs.conversations.services;
public class MediaPlayer extends android.media.MediaPlayer {
private int streamType;
@Override
public void setAudioStreamType(int streamType) {
this.streamType = streamType;
super.setAudioStreamType(streamType);
}
public int getAudioStreamType() {
return streamType;
}
}

View file

@ -1771,6 +1771,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
final Activity activity = getActivity(); final Activity activity = getActivity();
messageListAdapter.unregisterListenerInAudioPlayer();
if (activity == null || !activity.isChangingConfigurations()) { if (activity == null || !activity.isChangingConfigurations()) {
hideSoftKeyboard(activity); hideSoftKeyboard(activity);
messageListAdapter.stopAudioPlayer(); messageListAdapter.stopAudioPlayer();

View file

@ -877,6 +877,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
audioPlayer.stop(); audioPlayer.stop();
} }
public void unregisterListenerInAudioPlayer() {
audioPlayer.unregisterListener();
}
public void startStopPending() { public void startStopPending() {
audioPlayer.startStopPending(); audioPlayer.startStopPending();
} }

View file

@ -1,13 +1,20 @@
package eu.siacs.conversations.ui.service; package eu.siacs.conversations.ui.service;
import android.Manifest; import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.media.MediaPlayer; import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.AudioManager;
import android.os.Build; import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.PowerManager;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
@ -17,14 +24,16 @@ import android.widget.TextView;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Locale; import java.util.Locale;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.MediaPlayer;
import eu.siacs.conversations.ui.ConversationsActivity; import eu.siacs.conversations.ui.ConversationsActivity;
import eu.siacs.conversations.ui.adapter.MessageAdapter; import eu.siacs.conversations.ui.adapter.MessageAdapter;
import eu.siacs.conversations.ui.util.PendingItem; import eu.siacs.conversations.ui.util.PendingItem;
import eu.siacs.conversations.utils.WeakReferenceSet; import eu.siacs.conversations.utils.WeakReferenceSet;
public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompletionListener, SeekBar.OnSeekBarChangeListener, Runnable { public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompletionListener, SeekBar.OnSeekBarChangeListener, Runnable, SensorEventListener {
private static final int REFRESH_INTERVAL = 250; private static final int REFRESH_INTERVAL = 250;
private static final Object LOCK = new Object(); private static final Object LOCK = new Object();
@ -32,16 +41,36 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
private static Message currentlyPlayingMessage = null; private static Message currentlyPlayingMessage = null;
private final MessageAdapter messageAdapter; private final MessageAdapter messageAdapter;
private final WeakReferenceSet<RelativeLayout> audioPlayerLayouts = new WeakReferenceSet<>(); private final WeakReferenceSet<RelativeLayout> audioPlayerLayouts = new WeakReferenceSet<>();
private final SensorManager sensorManager;
private final Sensor proximitySensor;
private static PowerManager.WakeLock wakeLock;
private final PendingItem<WeakReference<ImageButton>> pendingOnClickView = new PendingItem<>(); private final PendingItem<WeakReference<ImageButton>> pendingOnClickView = new PendingItem<>();
private final Handler handler = new Handler(); private final Handler handler = new Handler();
public AudioPlayer(MessageAdapter adapter) { public AudioPlayer(MessageAdapter adapter) {
final Context context = adapter.getContext();
this.messageAdapter = adapter; this.messageAdapter = adapter;
this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
this.proximitySensor = this.sensorManager == null ? null : this.sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
if (Build.VERSION.SDK_INT >= 21) {
synchronized (AudioPlayer.LOCK) {
if (AudioPlayer.wakeLock == null) {
final PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
AudioPlayer.wakeLock = powerManager == null ? null : powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, AudioPlayer.class.getSimpleName());
AudioPlayer.wakeLock.setReferenceCounted(false);
}
}
} else {
AudioPlayer.wakeLock = null;
}
synchronized (AudioPlayer.LOCK) { synchronized (AudioPlayer.LOCK) {
if (AudioPlayer.player != null) { if (AudioPlayer.player != null) {
AudioPlayer.player.setOnCompletionListener(this); AudioPlayer.player.setOnCompletionListener(this);
if (AudioPlayer.player.isPlaying() && sensorManager != null) {
sensorManager.registerListener(this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
}
} }
} }
} }
@ -125,32 +154,45 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
viewHolder.progress.setEnabled(false); viewHolder.progress.setEnabled(false);
player.pause(); player.pause();
messageAdapter.flagScreenOff(); messageAdapter.flagScreenOff();
releaseProximityWakeLock();
viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp); viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
} else { } else {
viewHolder.progress.setEnabled(true); viewHolder.progress.setEnabled(true);
player.start(); player.start();
messageAdapter.flagScreenOn(); messageAdapter.flagScreenOn();
acquireProximityWakeLock();
this.stopRefresher(true); this.stopRefresher(true);
viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp); viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
} }
return false; return false;
} }
private boolean play(ViewHolder viewHolder, Message message) { private void play(ViewHolder viewHolder, Message message, boolean earpiece, double progress) {
if (play(viewHolder, message, earpiece)) {
AudioPlayer.player.seekTo((int) (AudioPlayer.player.getDuration() * progress));
}
}
private boolean play(ViewHolder viewHolder, Message message, boolean earpiece) {
AudioPlayer.player = new MediaPlayer(); AudioPlayer.player = new MediaPlayer();
try { try {
AudioPlayer.currentlyPlayingMessage = message; AudioPlayer.currentlyPlayingMessage = message;
AudioPlayer.player.setAudioStreamType(earpiece ? AudioManager.STREAM_VOICE_CALL : AudioManager.STREAM_MUSIC);
AudioPlayer.player.setDataSource(messageAdapter.getFileBackend().getFile(message).getAbsolutePath()); AudioPlayer.player.setDataSource(messageAdapter.getFileBackend().getFile(message).getAbsolutePath());
AudioPlayer.player.setOnCompletionListener(this); AudioPlayer.player.setOnCompletionListener(this);
AudioPlayer.player.prepare(); AudioPlayer.player.prepare();
AudioPlayer.player.start(); AudioPlayer.player.start();
messageAdapter.flagScreenOn(); messageAdapter.flagScreenOn();
acquireProximityWakeLock();
viewHolder.progress.setEnabled(true); viewHolder.progress.setEnabled(true);
viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp); viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
sensorManager.registerListener(this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
messageAdapter.flagScreenOff(); messageAdapter.flagScreenOff();
releaseProximityWakeLock();
AudioPlayer.currentlyPlayingMessage = null; AudioPlayer.currentlyPlayingMessage = null;
sensorManager.unregisterListener(this);
return false; return false;
} }
} }
@ -172,7 +214,7 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
if (AudioPlayer.player != null) { if (AudioPlayer.player != null) {
stopCurrent(); stopCurrent();
} }
return play(viewHolder, message); return play(viewHolder, message, false);
} }
private void stopCurrent() { private void stopCurrent() {
@ -181,6 +223,7 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
} }
AudioPlayer.player.release(); AudioPlayer.player.release();
messageAdapter.flagScreenOff(); messageAdapter.flagScreenOff();
releaseProximityWakeLock();
AudioPlayer.player = null; AudioPlayer.player = null;
resetPlayerUi(); resetPlayerUi();
} }
@ -206,7 +249,7 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
} }
@Override @Override
public void onCompletion(MediaPlayer mediaPlayer) { public void onCompletion(android.media.MediaPlayer mediaPlayer) {
synchronized (AudioPlayer.LOCK) { synchronized (AudioPlayer.LOCK) {
this.stopRefresher(false); this.stopRefresher(false);
if (AudioPlayer.player == mediaPlayer) { if (AudioPlayer.player == mediaPlayer) {
@ -215,7 +258,9 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
} }
mediaPlayer.release(); mediaPlayer.release();
messageAdapter.flagScreenOff(); messageAdapter.flagScreenOff();
releaseProximityWakeLock();
resetPlayerUi(); resetPlayerUi();
sensorManager.unregisterListener(this);
} }
} }
@ -250,6 +295,11 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
stopCurrent(); stopCurrent();
} }
AudioPlayer.currentlyPlayingMessage = null; AudioPlayer.currentlyPlayingMessage = null;
sensorManager.unregisterListener(this);
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
}
wakeLock = null;
} }
} }
@ -260,6 +310,12 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
} }
} }
public void unregisterListener() {
if (sensorManager != null) {
sensorManager.unregisterListener(this);
}
}
@Override @Override
public void run() { public void run() {
synchronized (AudioPlayer.LOCK) { synchronized (AudioPlayer.LOCK) {
@ -287,6 +343,70 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
return true; return true;
} }
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() != Sensor.TYPE_PROXIMITY) {
return;
}
if (AudioPlayer.player == null || !AudioPlayer.player.isPlaying()) {
return;
}
int streamType;
if (event.values[0] < 5f && event.values[0] != proximitySensor.getMaximumRange()) {
streamType = AudioManager.STREAM_VOICE_CALL;
} else {
streamType = AudioManager.STREAM_MUSIC;
}
double position = AudioPlayer.player.getCurrentPosition();
double duration = AudioPlayer.player.getDuration();
double progress = position / duration;
if (AudioPlayer.player.getAudioStreamType() != streamType) {
synchronized (AudioPlayer.LOCK) {
AudioPlayer.player.stop();
AudioPlayer.player.release();
AudioPlayer.player = null;
try {
ViewHolder currentViewHolder = getCurrentViewHolder();
if (currentViewHolder != null) {
play(currentViewHolder, currentlyPlayingMessage, streamType == AudioManager.STREAM_VOICE_CALL, progress);
}
} catch (Exception e) {
Log.w(Config.LOGTAG, e);
}
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
private void acquireProximityWakeLock() {
synchronized (AudioPlayer.LOCK) {
if (wakeLock != null) {
wakeLock.acquire();
}
}
}
private void releaseProximityWakeLock() {
synchronized (AudioPlayer.LOCK) {
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
}
}
}
private ViewHolder getCurrentViewHolder() {
for (WeakReference<RelativeLayout> audioPlayer : audioPlayerLayouts) {
final Message message = (Message) audioPlayer.get().getTag();
if (message == currentlyPlayingMessage) {
return ViewHolder.get(audioPlayer.get());
}
}
return null;
}
public static class ViewHolder { public static class ViewHolder {
private TextView runtime; private TextView runtime;
private SeekBar progress; private SeekBar progress;
@ -297,9 +417,9 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
ViewHolder viewHolder = (ViewHolder) audioPlayer.getTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER); ViewHolder viewHolder = (ViewHolder) audioPlayer.getTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER);
if (viewHolder == null) { if (viewHolder == null) {
viewHolder = new ViewHolder(); viewHolder = new ViewHolder();
viewHolder.runtime = (TextView) audioPlayer.findViewById(R.id.runtime); viewHolder.runtime = audioPlayer.findViewById(R.id.runtime);
viewHolder.progress = (SeekBar) audioPlayer.findViewById(R.id.progress); viewHolder.progress = audioPlayer.findViewById(R.id.progress);
viewHolder.playPause = (ImageButton) audioPlayer.findViewById(R.id.play_pause); viewHolder.playPause = audioPlayer.findViewById(R.id.play_pause);
audioPlayer.setTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER, viewHolder); audioPlayer.setTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER, viewHolder);
} }
return viewHolder; return viewHolder;