check if wake lock needs reinit when pressing start/pause in audio player

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/ui/service/AudioPlayer.java | 792 +
1 file changed, 398 insertions(+), 394 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/ui/service/AudioPlayer.java 🔗

@@ -35,398 +35,402 @@ import eu.siacs.conversations.utils.WeakReferenceSet;
 
 public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompletionListener, SeekBar.OnSeekBarChangeListener, Runnable, SensorEventListener {
 
-	private static final int REFRESH_INTERVAL = 250;
-	private static final Object LOCK = new Object();
-	private static MediaPlayer player = null;
-	private static Message currentlyPlayingMessage = null;
-	private final MessageAdapter messageAdapter;
-	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 Handler handler = new Handler();
-
-	public AudioPlayer(MessageAdapter adapter) {
-		final Context context = adapter.getContext();
-		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) {
-			if (AudioPlayer.player != null) {
-				AudioPlayer.player.setOnCompletionListener(this);
-				if (AudioPlayer.player.isPlaying() && sensorManager != null) {
-					sensorManager.registerListener(this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
-				}
-			}
-		}
-	}
-
-	private static String formatTime(int ms) {
-		return String.format(Locale.ENGLISH, "%d:%02d", ms / 60000, Math.min(Math.round((ms % 60000) / 1000f), 59));
-	}
-
-	public void init(RelativeLayout audioPlayer, Message message) {
-		synchronized (AudioPlayer.LOCK) {
-			audioPlayer.setTag(message);
-			if (init(ViewHolder.get(audioPlayer), message)) {
-				this.audioPlayerLayouts.addWeakReferenceTo(audioPlayer);
-				this.stopRefresher(true);
-			} else {
-				this.audioPlayerLayouts.removeWeakReferenceTo(audioPlayer);
-			}
-		}
-	}
-
-	private boolean init(ViewHolder viewHolder, Message message) {
-		if (viewHolder.darkBackground) {
-			viewHolder.runtime.setTextAppearance(this.messageAdapter.getContext(), R.style.TextAppearance_Conversations_Caption_OnDark);
-		} else {
-			viewHolder.runtime.setTextAppearance(this.messageAdapter.getContext(), R.style.TextAppearance_Conversations_Caption);
-		}
-		viewHolder.progress.setOnSeekBarChangeListener(this);
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-			ColorStateList color = ContextCompat.getColorStateList(messageAdapter.getContext(), viewHolder.darkBackground ? R.color.white70 : R.color.green700_desaturated);
-			viewHolder.progress.setThumbTintList(color);
-			viewHolder.progress.setProgressTintList(color);
-		}
-		viewHolder.playPause.setAlpha(viewHolder.darkBackground ? 0.7f : 0.57f);
-		viewHolder.playPause.setOnClickListener(this);
-		if (message == currentlyPlayingMessage) {
-			if (AudioPlayer.player != null && AudioPlayer.player.isPlaying()) {
-				viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
-				viewHolder.progress.setEnabled(true);
-			} else {
-				viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
-				viewHolder.progress.setEnabled(false);
-			}
-			return true;
-		} else {
-			viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
-			viewHolder.runtime.setText(formatTime(message.getFileParams().runtime));
-			viewHolder.progress.setProgress(0);
-			viewHolder.progress.setEnabled(false);
-			return false;
-		}
-	}
-
-	@Override
-	public synchronized void onClick(View v) {
-		if (v.getId() == R.id.play_pause) {
-			synchronized (LOCK) {
-				startStop((ImageButton) v);
-			}
-		}
-	}
-
-	private void startStop(ImageButton playPause) {
-		if (ContextCompat.checkSelfPermission(messageAdapter.getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
-			pendingOnClickView.push(new WeakReference<>(playPause));
-			ActivityCompat.requestPermissions(messageAdapter.getActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, ConversationsActivity.REQUEST_PLAY_PAUSE);
-			return;
-		}
-		final RelativeLayout audioPlayer = (RelativeLayout) playPause.getParent();
-		final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
-		final Message message = (Message) audioPlayer.getTag();
-		if (startStop(viewHolder, message)) {
-			this.audioPlayerLayouts.clear();
-			this.audioPlayerLayouts.addWeakReferenceTo(audioPlayer);
-			stopRefresher(true);
-		}
-	}
-
-	private boolean playPauseCurrent(ViewHolder viewHolder) {
-		viewHolder.playPause.setAlpha(viewHolder.darkBackground ? 0.7f : 0.57f);
-		if (player.isPlaying()) {
-			viewHolder.progress.setEnabled(false);
-			player.pause();
-			messageAdapter.flagScreenOff();
-			releaseProximityWakeLock();
-			viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
-		} else {
-			viewHolder.progress.setEnabled(true);
-			player.start();
-			messageAdapter.flagScreenOn();
-			acquireProximityWakeLock();
-			this.stopRefresher(true);
-			viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
-		}
-		return false;
-	}
-
-	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();
-		try {
-			AudioPlayer.currentlyPlayingMessage = message;
-			AudioPlayer.player.setAudioStreamType(earpiece ? AudioManager.STREAM_VOICE_CALL : AudioManager.STREAM_MUSIC);
-			AudioPlayer.player.setDataSource(messageAdapter.getFileBackend().getFile(message).getAbsolutePath());
-			AudioPlayer.player.setOnCompletionListener(this);
-			AudioPlayer.player.prepare();
-			AudioPlayer.player.start();
-			messageAdapter.flagScreenOn();
-			acquireProximityWakeLock();
-			viewHolder.progress.setEnabled(true);
-			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;
-		} catch (Exception e) {
-			messageAdapter.flagScreenOff();
-			releaseProximityWakeLock();
-			AudioPlayer.currentlyPlayingMessage = null;
-			sensorManager.unregisterListener(this);
-			return false;
-		}
-	}
-
-	public void startStopPending() {
-		WeakReference<ImageButton> reference = pendingOnClickView.pop();
-		if (reference != null) {
-			ImageButton imageButton = reference.get();
-			if (imageButton != null) {
-				startStop(imageButton);
-			}
-		}
-	}
-
-	private boolean startStop(ViewHolder viewHolder, Message message) {
-		if (message == currentlyPlayingMessage && player != null) {
-			return playPauseCurrent(viewHolder);
-		}
-		if (AudioPlayer.player != null) {
-			stopCurrent();
-		}
-		return play(viewHolder, message, false);
-	}
-
-	private void stopCurrent() {
-		if (AudioPlayer.player.isPlaying()) {
-			AudioPlayer.player.stop();
-		}
-		AudioPlayer.player.release();
-		messageAdapter.flagScreenOff();
-		releaseProximityWakeLock();
-		AudioPlayer.player = null;
-		resetPlayerUi();
-	}
-
-	private void resetPlayerUi() {
-		for (WeakReference<RelativeLayout> audioPlayer : audioPlayerLayouts) {
-			resetPlayerUi(audioPlayer.get());
-		}
-	}
-
-	private void resetPlayerUi(RelativeLayout audioPlayer) {
-		if (audioPlayer == null) {
-			return;
-		}
-		final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
-		final Message message = (Message) audioPlayer.getTag();
-		viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
-		if (message != null) {
-			viewHolder.runtime.setText(formatTime(message.getFileParams().runtime));
-		}
-		viewHolder.progress.setProgress(0);
-		viewHolder.progress.setEnabled(false);
-	}
-
-	@Override
-	public void onCompletion(android.media.MediaPlayer mediaPlayer) {
-		synchronized (AudioPlayer.LOCK) {
-			this.stopRefresher(false);
-			if (AudioPlayer.player == mediaPlayer) {
-				AudioPlayer.currentlyPlayingMessage = null;
-				AudioPlayer.player = null;
-			}
-			mediaPlayer.release();
-			messageAdapter.flagScreenOff();
-			releaseProximityWakeLock();
-			resetPlayerUi();
-			sensorManager.unregisterListener(this);
-		}
-	}
-
-	@Override
-	public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-		synchronized (AudioPlayer.LOCK) {
-			final RelativeLayout audioPlayer = (RelativeLayout) seekBar.getParent();
-			final Message message = (Message) audioPlayer.getTag();
-			if (fromUser && message == AudioPlayer.currentlyPlayingMessage) {
-				float percent = progress / 100f;
-				int duration = AudioPlayer.player.getDuration();
-				int seekTo = Math.round(duration * percent);
-				AudioPlayer.player.seekTo(seekTo);
-			}
-		}
-	}
-
-	@Override
-	public void onStartTrackingTouch(SeekBar seekBar) {
-
-	}
-
-	@Override
-	public void onStopTrackingTouch(SeekBar seekBar) {
-
-	}
-
-	public void stop() {
-		synchronized (AudioPlayer.LOCK) {
-			stopRefresher(false);
-			if (AudioPlayer.player != null) {
-				stopCurrent();
-			}
-			AudioPlayer.currentlyPlayingMessage = null;
-			sensorManager.unregisterListener(this);
-			if (wakeLock != null && wakeLock.isHeld()) {
-				wakeLock.release();
-			}
-			wakeLock = null;
-		}
-	}
-
-	private void stopRefresher(boolean runOnceMore) {
-		this.handler.removeCallbacks(this);
-		if (runOnceMore) {
-			this.handler.post(this);
-		}
-	}
-
-	public void unregisterListener() {
-		if (sensorManager != null) {
-			sensorManager.unregisterListener(this);
-		}
-	}
-
-	@Override
-	public void run() {
-		synchronized (AudioPlayer.LOCK) {
-			if (AudioPlayer.player != null) {
-				boolean renew = false;
-				final int current = player.getCurrentPosition();
-				final int duration = player.getDuration();
-				for (WeakReference<RelativeLayout> audioPlayer : audioPlayerLayouts) {
-					renew |= refreshAudioPlayer(audioPlayer.get(), current, duration);
-				}
-				if (renew && AudioPlayer.player.isPlaying()) {
-					handler.postDelayed(this, REFRESH_INTERVAL);
-				}
-			}
-		}
-	}
-
-	private boolean refreshAudioPlayer(RelativeLayout audioPlayer, int current, int duration) {
-		if (audioPlayer == null || audioPlayer.getVisibility() != View.VISIBLE) {
-			return false;
-		}
-		final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
-		viewHolder.progress.setProgress(current * 100 / duration);
-		viewHolder.runtime.setText(formatTime(current) + " / " + formatTime(duration));
-		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 {
-		private TextView runtime;
-		private SeekBar progress;
-		private ImageButton playPause;
-		private boolean darkBackground = false;
-
-		public static ViewHolder get(RelativeLayout audioPlayer) {
-			ViewHolder viewHolder = (ViewHolder) audioPlayer.getTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER);
-			if (viewHolder == null) {
-				viewHolder = new ViewHolder();
-				viewHolder.runtime = audioPlayer.findViewById(R.id.runtime);
-				viewHolder.progress = audioPlayer.findViewById(R.id.progress);
-				viewHolder.playPause = audioPlayer.findViewById(R.id.play_pause);
-				audioPlayer.setTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER, viewHolder);
-			}
-			return viewHolder;
-		}
-
-		public void setDarkBackground(boolean darkBackground) {
-			this.darkBackground = darkBackground;
-		}
-	}
+    private static final int REFRESH_INTERVAL = 250;
+    private static final Object LOCK = new Object();
+    private static MediaPlayer player = null;
+    private static Message currentlyPlayingMessage = null;
+    private static PowerManager.WakeLock wakeLock;
+    private final MessageAdapter messageAdapter;
+    private final WeakReferenceSet<RelativeLayout> audioPlayerLayouts = new WeakReferenceSet<>();
+    private final SensorManager sensorManager;
+    private final Sensor proximitySensor;
+    private final PendingItem<WeakReference<ImageButton>> pendingOnClickView = new PendingItem<>();
+
+    private final Handler handler = new Handler();
+
+    public AudioPlayer(MessageAdapter adapter) {
+        final Context context = adapter.getContext();
+        this.messageAdapter = adapter;
+        this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+        this.proximitySensor = this.sensorManager == null ? null : this.sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+        initializeProximityWakeLock(context);
+        synchronized (AudioPlayer.LOCK) {
+            if (AudioPlayer.player != null) {
+                AudioPlayer.player.setOnCompletionListener(this);
+                if (AudioPlayer.player.isPlaying() && sensorManager != null) {
+                    sensorManager.registerListener(this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
+                }
+            }
+        }
+    }
+
+    private static String formatTime(int ms) {
+        return String.format(Locale.ENGLISH, "%d:%02d", ms / 60000, Math.min(Math.round((ms % 60000) / 1000f), 59));
+    }
+
+    private void initializeProximityWakeLock(Context context) {
+        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;
+        }
+    }
+
+    public void init(RelativeLayout audioPlayer, Message message) {
+        synchronized (AudioPlayer.LOCK) {
+            audioPlayer.setTag(message);
+            if (init(ViewHolder.get(audioPlayer), message)) {
+                this.audioPlayerLayouts.addWeakReferenceTo(audioPlayer);
+                this.stopRefresher(true);
+            } else {
+                this.audioPlayerLayouts.removeWeakReferenceTo(audioPlayer);
+            }
+        }
+    }
+
+    private boolean init(ViewHolder viewHolder, Message message) {
+        if (viewHolder.darkBackground) {
+            viewHolder.runtime.setTextAppearance(this.messageAdapter.getContext(), R.style.TextAppearance_Conversations_Caption_OnDark);
+        } else {
+            viewHolder.runtime.setTextAppearance(this.messageAdapter.getContext(), R.style.TextAppearance_Conversations_Caption);
+        }
+        viewHolder.progress.setOnSeekBarChangeListener(this);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            ColorStateList color = ContextCompat.getColorStateList(messageAdapter.getContext(), viewHolder.darkBackground ? R.color.white70 : R.color.green700_desaturated);
+            viewHolder.progress.setThumbTintList(color);
+            viewHolder.progress.setProgressTintList(color);
+        }
+        viewHolder.playPause.setAlpha(viewHolder.darkBackground ? 0.7f : 0.57f);
+        viewHolder.playPause.setOnClickListener(this);
+        if (message == currentlyPlayingMessage) {
+            if (AudioPlayer.player != null && AudioPlayer.player.isPlaying()) {
+                viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
+                viewHolder.progress.setEnabled(true);
+            } else {
+                viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
+                viewHolder.progress.setEnabled(false);
+            }
+            return true;
+        } else {
+            viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
+            viewHolder.runtime.setText(formatTime(message.getFileParams().runtime));
+            viewHolder.progress.setProgress(0);
+            viewHolder.progress.setEnabled(false);
+            return false;
+        }
+    }
+
+    @Override
+    public synchronized void onClick(View v) {
+        if (v.getId() == R.id.play_pause) {
+            synchronized (LOCK) {
+                startStop((ImageButton) v);
+            }
+        }
+    }
+
+    private void startStop(ImageButton playPause) {
+        if (ContextCompat.checkSelfPermission(messageAdapter.getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+            pendingOnClickView.push(new WeakReference<>(playPause));
+            ActivityCompat.requestPermissions(messageAdapter.getActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, ConversationsActivity.REQUEST_PLAY_PAUSE);
+            return;
+        }
+        initializeProximityWakeLock(playPause.getContext());
+        final RelativeLayout audioPlayer = (RelativeLayout) playPause.getParent();
+        final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
+        final Message message = (Message) audioPlayer.getTag();
+        if (startStop(viewHolder, message)) {
+            this.audioPlayerLayouts.clear();
+            this.audioPlayerLayouts.addWeakReferenceTo(audioPlayer);
+            stopRefresher(true);
+        }
+    }
+
+    private boolean playPauseCurrent(ViewHolder viewHolder) {
+        viewHolder.playPause.setAlpha(viewHolder.darkBackground ? 0.7f : 0.57f);
+        if (player.isPlaying()) {
+            viewHolder.progress.setEnabled(false);
+            player.pause();
+            messageAdapter.flagScreenOff();
+            releaseProximityWakeLock();
+            viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
+        } else {
+            viewHolder.progress.setEnabled(true);
+            player.start();
+            messageAdapter.flagScreenOn();
+            acquireProximityWakeLock();
+            this.stopRefresher(true);
+            viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
+        }
+        return false;
+    }
+
+    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();
+        try {
+            AudioPlayer.currentlyPlayingMessage = message;
+            AudioPlayer.player.setAudioStreamType(earpiece ? AudioManager.STREAM_VOICE_CALL : AudioManager.STREAM_MUSIC);
+            AudioPlayer.player.setDataSource(messageAdapter.getFileBackend().getFile(message).getAbsolutePath());
+            AudioPlayer.player.setOnCompletionListener(this);
+            AudioPlayer.player.prepare();
+            AudioPlayer.player.start();
+            messageAdapter.flagScreenOn();
+            acquireProximityWakeLock();
+            viewHolder.progress.setEnabled(true);
+            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;
+        } catch (Exception e) {
+            messageAdapter.flagScreenOff();
+            releaseProximityWakeLock();
+            AudioPlayer.currentlyPlayingMessage = null;
+            sensorManager.unregisterListener(this);
+            return false;
+        }
+    }
+
+    public void startStopPending() {
+        WeakReference<ImageButton> reference = pendingOnClickView.pop();
+        if (reference != null) {
+            ImageButton imageButton = reference.get();
+            if (imageButton != null) {
+                startStop(imageButton);
+            }
+        }
+    }
+
+    private boolean startStop(ViewHolder viewHolder, Message message) {
+        if (message == currentlyPlayingMessage && player != null) {
+            return playPauseCurrent(viewHolder);
+        }
+        if (AudioPlayer.player != null) {
+            stopCurrent();
+        }
+        return play(viewHolder, message, false);
+    }
+
+    private void stopCurrent() {
+        if (AudioPlayer.player.isPlaying()) {
+            AudioPlayer.player.stop();
+        }
+        AudioPlayer.player.release();
+        messageAdapter.flagScreenOff();
+        releaseProximityWakeLock();
+        AudioPlayer.player = null;
+        resetPlayerUi();
+    }
+
+    private void resetPlayerUi() {
+        for (WeakReference<RelativeLayout> audioPlayer : audioPlayerLayouts) {
+            resetPlayerUi(audioPlayer.get());
+        }
+    }
+
+    private void resetPlayerUi(RelativeLayout audioPlayer) {
+        if (audioPlayer == null) {
+            return;
+        }
+        final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
+        final Message message = (Message) audioPlayer.getTag();
+        viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
+        if (message != null) {
+            viewHolder.runtime.setText(formatTime(message.getFileParams().runtime));
+        }
+        viewHolder.progress.setProgress(0);
+        viewHolder.progress.setEnabled(false);
+    }
+
+    @Override
+    public void onCompletion(android.media.MediaPlayer mediaPlayer) {
+        synchronized (AudioPlayer.LOCK) {
+            this.stopRefresher(false);
+            if (AudioPlayer.player == mediaPlayer) {
+                AudioPlayer.currentlyPlayingMessage = null;
+                AudioPlayer.player = null;
+            }
+            mediaPlayer.release();
+            messageAdapter.flagScreenOff();
+            releaseProximityWakeLock();
+            resetPlayerUi();
+            sensorManager.unregisterListener(this);
+        }
+    }
+
+    @Override
+    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+        synchronized (AudioPlayer.LOCK) {
+            final RelativeLayout audioPlayer = (RelativeLayout) seekBar.getParent();
+            final Message message = (Message) audioPlayer.getTag();
+            if (fromUser && message == AudioPlayer.currentlyPlayingMessage) {
+                float percent = progress / 100f;
+                int duration = AudioPlayer.player.getDuration();
+                int seekTo = Math.round(duration * percent);
+                AudioPlayer.player.seekTo(seekTo);
+            }
+        }
+    }
+
+    @Override
+    public void onStartTrackingTouch(SeekBar seekBar) {
+
+    }
+
+    @Override
+    public void onStopTrackingTouch(SeekBar seekBar) {
+
+    }
+
+    public void stop() {
+        synchronized (AudioPlayer.LOCK) {
+            stopRefresher(false);
+            if (AudioPlayer.player != null) {
+                stopCurrent();
+            }
+            AudioPlayer.currentlyPlayingMessage = null;
+            sensorManager.unregisterListener(this);
+            if (wakeLock != null && wakeLock.isHeld()) {
+                wakeLock.release();
+            }
+            wakeLock = null;
+        }
+    }
+
+    private void stopRefresher(boolean runOnceMore) {
+        this.handler.removeCallbacks(this);
+        if (runOnceMore) {
+            this.handler.post(this);
+        }
+    }
+
+    public void unregisterListener() {
+        if (sensorManager != null) {
+            sensorManager.unregisterListener(this);
+        }
+    }
+
+    @Override
+    public void run() {
+        synchronized (AudioPlayer.LOCK) {
+            if (AudioPlayer.player != null) {
+                boolean renew = false;
+                final int current = player.getCurrentPosition();
+                final int duration = player.getDuration();
+                for (WeakReference<RelativeLayout> audioPlayer : audioPlayerLayouts) {
+                    renew |= refreshAudioPlayer(audioPlayer.get(), current, duration);
+                }
+                if (renew && AudioPlayer.player.isPlaying()) {
+                    handler.postDelayed(this, REFRESH_INTERVAL);
+                }
+            }
+        }
+    }
+
+    private boolean refreshAudioPlayer(RelativeLayout audioPlayer, int current, int duration) {
+        if (audioPlayer == null || audioPlayer.getVisibility() != View.VISIBLE) {
+            return false;
+        }
+        final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
+        viewHolder.progress.setProgress(current * 100 / duration);
+        viewHolder.runtime.setText(formatTime(current) + " / " + formatTime(duration));
+        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 {
+        private TextView runtime;
+        private SeekBar progress;
+        private ImageButton playPause;
+        private boolean darkBackground = false;
+
+        public static ViewHolder get(RelativeLayout audioPlayer) {
+            ViewHolder viewHolder = (ViewHolder) audioPlayer.getTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER);
+            if (viewHolder == null) {
+                viewHolder = new ViewHolder();
+                viewHolder.runtime = audioPlayer.findViewById(R.id.runtime);
+                viewHolder.progress = audioPlayer.findViewById(R.id.progress);
+                viewHolder.playPause = audioPlayer.findViewById(R.id.play_pause);
+                audioPlayer.setTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER, viewHolder);
+            }
+            return viewHolder;
+        }
+
+        public void setDarkBackground(boolean darkBackground) {
+            this.darkBackground = darkBackground;
+        }
+    }
 }