summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
diff options
context:
space:
mode:
authoruabdullah <uabdullah@google.com>2017-12-11 15:20:15 -0800
committerCopybara-Service <copybara-piper@google.com>2017-12-11 15:23:08 -0800
commit1fab0cca2259a7f8dd0b4349bc38f282eb4209a8 (patch)
treea677be3a11bd33ba9aa9903cc868ec607fc213f4 /java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
parentc875a5b7630376c767d86b708e4aab8382de8acc (diff)
Play voicemails, update seekbar timer, allow seeking, and maintain state after recycling.
This CL adds the support for playing voicemails, changing the play button to pause button when playing the voicemail, updating the seekbar and duration timer when the voicemail is being played. It also adds the support to preserve the state of the media player such that when scrolling and recycling views, when an expanded and playing voicemail is recycled back into view, it's most recent state is shown i.e the duration and the seekbar are upto date. Video: https://drive.google.com/open?id=1CKbLK5-1YDeXBZFiKvuTxoPuFJQ1rbj7 Test: Unit tests PiperOrigin-RevId: 178681663 Change-Id: Ifdd1d945572926bdc7d652aa7a876d3156fc21ce
Diffstat (limited to 'java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java')
-rw-r--r--java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java395
1 files changed, 334 insertions, 61 deletions
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
index d5db60846..77dd9cc4b 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
@@ -19,12 +19,9 @@ package com.android.dialer.voicemail.listui;
import android.app.FragmentManager;
import android.content.Context;
import android.database.Cursor;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.media.MediaPlayer.OnErrorListener;
-import android.media.MediaPlayer.OnPreparedListener;
import android.net.Uri;
import android.provider.VoicemailContract;
+import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.support.v4.util.Pair;
import android.util.AttributeSet;
@@ -32,13 +29,18 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageButton;
import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
+import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener;
import com.android.dialer.common.concurrent.DialerExecutor.Worker;
import com.android.dialer.common.concurrent.DialerExecutorComponent;
+import com.android.dialer.voicemail.listui.NewVoicemailViewHolder.NewVoicemailViewHolderListener;
import com.android.dialer.voicemail.model.VoicemailEntry;
+import java.util.Locale;
/**
* The view of the media player that is visible when a {@link NewVoicemailViewHolder} is expanded.
@@ -46,13 +48,18 @@ import com.android.dialer.voicemail.model.VoicemailEntry;
public class NewVoicemailMediaPlayerView extends LinearLayout {
private ImageButton playButton;
+ private ImageButton pauseButton;
private ImageButton speakerButton;
private ImageButton phoneButton;
private ImageButton deleteButton;
+ private TextView currentSeekBarPosition;
+ private SeekBar seekBarView;
private TextView totalDurationView;
private Uri voicemailUri;
private FragmentManager fragmentManager;
- private MediaPlayer mediaPlayer;
+ private NewVoicemailViewHolder newVoicemailViewHolder;
+ private NewVoicemailMediaPlayer mediaPlayer;
+ private NewVoicemailViewHolderListener newVoicemailViewHolderListener;
public NewVoicemailMediaPlayerView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -72,6 +79,9 @@ public class NewVoicemailMediaPlayerView extends LinearLayout {
private void initializeMediaPlayerButtonsAndViews() {
playButton = findViewById(R.id.playButton);
+ pauseButton = findViewById(R.id.pauseButton);
+ currentSeekBarPosition = findViewById(R.id.playback_position_text);
+ seekBarView = findViewById(R.id.playback_seek);
speakerButton = findViewById(R.id.speakerButton);
phoneButton = findViewById(R.id.phoneButton);
deleteButton = findViewById(R.id.deleteButton);
@@ -80,13 +90,202 @@ public class NewVoicemailMediaPlayerView extends LinearLayout {
private void setupListenersForMediaPlayerButtons() {
playButton.setOnClickListener(playButtonListener);
+ pauseButton.setOnClickListener(pauseButtonListener);
+ seekBarView.setOnSeekBarChangeListener(seekbarChangeListener);
speakerButton.setOnClickListener(speakerButtonListener);
phoneButton.setOnClickListener(phoneButtonListener);
deleteButton.setOnClickListener(deleteButtonListener);
}
+ public void reset() {
+ LogUtil.i("NewVoicemailMediaPlayer.reset", "the uri for this is " + voicemailUri);
+ voicemailUri = null;
+ }
+
+ /**
+ * Can be called either when binding happens on the {@link NewVoicemailViewHolder} from {@link
+ * NewVoicemailAdapter} or when a user expands a {@link NewVoicemailViewHolder}. During the
+ * binding, since {@link NewVoicemailMediaPlayerView} is part of {@link NewVoicemailViewHolder},
+ * we have to ensure that during the binding the values from the {@link NewVoicemailAdapter} are
+ * also propogated down to the {@link NewVoicemailMediaPlayerView} via {@link
+ * NewVoicemailViewHolder}. In the case of when the {@link NewVoicemailViewHolder} is expanded,
+ * the most recent value and states from the {@link NewVoicemailAdapter} are set for the expanded
+ * {@link NewVoicemailMediaPlayerView}.
+ *
+ * @param viewHolder
+ * @param voicemailEntryFromAdapter are the voicemail related values from the {@link
+ * AnnotatedCallLog} converted into {@link VoicemailEntry} format.
+ * @param fragmentManager
+ * @param mp the media player passed down from the adapter
+ * @param listener
+ */
+ void bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView(
+ NewVoicemailViewHolder viewHolder,
+ @NonNull VoicemailEntry voicemailEntryFromAdapter,
+ @NonNull FragmentManager fragmentManager,
+ NewVoicemailMediaPlayer mp,
+ NewVoicemailViewHolderListener listener) {
+
+ Assert.isNotNull(voicemailEntryFromAdapter);
+ Uri uri = Uri.parse(voicemailEntryFromAdapter.voicemailUri());
+ Assert.isNotNull(viewHolder);
+ Assert.isNotNull(uri);
+ Assert.isNotNull(listener);
+ Assert.isNotNull(totalDurationView);
+ Assert.checkArgument(uri.equals(viewHolder.getViewHolderVoicemailUri()));
+
+ LogUtil.i(
+ "NewVoicemailMediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView",
+ "Updating the viewholder:%d mediaPlayerView with uri value:%s",
+ viewHolder.getViewHolderId(),
+ uri.toString());
+
+ this.fragmentManager = fragmentManager;
+
+ newVoicemailViewHolder = viewHolder;
+ newVoicemailViewHolderListener = listener;
+ mediaPlayer = mp;
+ voicemailUri = uri;
+ totalDurationView.setText(
+ VoicemailEntryText.getVoicemailDuration(getContext(), voicemailEntryFromAdapter));
+ // Not sure if these are needed, but it'll ensure that onInflate() has atleast happened.
+ initializeMediaPlayerButtonsAndViews();
+ setupListenersForMediaPlayerButtons();
+
+ // During the binding we only send a request to the adapter to tell us what the
+ // state of the media player should be and call that function.
+ // This could be the paused state, or the playing state of the resume state.
+ // Our job here is only to send the request upto the adapter and have it decide what we should
+ // do.
+ LogUtil.i(
+ "NewVoicemailMediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView",
+ "Updating media player values for id:" + viewHolder.getViewHolderId());
+
+ // During the binding make sure that the first time we just set the mediaplayer view
+ // This does not take care of the constant update
+ if (mp.isPlaying() && mp.getLastPlayedOrPlayingVoicemailUri().equals(voicemailUri)) {
+ Assert.checkArgument(
+ mp.getLastPlayedOrPlayingVoicemailUri()
+ .equals(mp.getLastPreparedOrPreparingToPlayVoicemailUri()));
+ LogUtil.i(
+ "NewVoicemailMediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView",
+ "show playing state");
+ playButton.setVisibility(GONE);
+ pauseButton.setVisibility(VISIBLE);
+ currentSeekBarPosition.setText(formatAsMinutesAndSeconds(mp.getCurrentPosition()));
+
+ if (seekBarView.getMax() != mp.getDuration()) {
+ seekBarView.setMax(mp.getDuration());
+ }
+ seekBarView.setProgress(mp.getCurrentPosition());
+
+ } else if (mediaPlayer.isPaused() && mp.getLastPausedVoicemailUri().equals(voicemailUri)) {
+ LogUtil.i(
+ "NewVoicemailMediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView",
+ "show paused state");
+ Assert.checkArgument(viewHolder.getViewHolderVoicemailUri().equals(voicemailUri));
+ playButton.setVisibility(VISIBLE);
+ pauseButton.setVisibility(GONE);
+ currentSeekBarPosition.setText(formatAsMinutesAndSeconds(mp.getCurrentPosition()));
+ if (seekBarView.getMax() != mp.getDuration()) {
+ seekBarView.setMax(mp.getDuration());
+ }
+ seekBarView.setProgress(mp.getCurrentPosition());
+
+ } else {
+ LogUtil.i(
+ "NewVoicemailMediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView",
+ "show reset state");
+ playButton.setVisibility(VISIBLE);
+ pauseButton.setVisibility(GONE);
+ seekBarView.setProgress(0);
+ seekBarView.setMax(100);
+ currentSeekBarPosition.setText(formatAsMinutesAndSeconds(0));
+ }
+ }
+
+ private final OnSeekBarChangeListener seekbarChangeListener =
+ new OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBarfromProgress, int progress, boolean fromUser) {
+ // TODO(uabdullah): Only for debugging purposes, to be removed.
+ if (progress < 100) {
+ LogUtil.i(
+ "NewVoicemailMediaPlayer.seekbarChangeListener",
+ "onProgressChanged, progress:%d, seekbarMax: %d, fromUser:%b",
+ progress,
+ seekBarfromProgress.getMax(),
+ fromUser);
+ }
+
+ if (fromUser) {
+ mediaPlayer.seekTo(progress);
+ currentSeekBarPosition.setText(formatAsMinutesAndSeconds(progress));
+ }
+ }
+
+ @Override
+ // TODO(uabdullah): Handle this case
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ LogUtil.i("NewVoicemailMediaPlayer.onStartTrackingTouch", "does nothing for now");
+ }
+
+ @Override
+ // TODO(uabdullah): Handle this case
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ LogUtil.i("NewVoicemailMediaPlayer.onStopTrackingTouch", "does nothing for now");
+ }
+ };
+
+ private final View.OnClickListener pauseButtonListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ LogUtil.i(
+ "NewVoicemailMediaPlayer.pauseButtonListener",
+ "pauseMediaPlayerAndSetPausedStateOfViewHolder button for voicemailUri: %s",
+ voicemailUri.toString());
+
+ Assert.checkArgument(playButton.getVisibility() == GONE);
+ Assert.checkArgument(mediaPlayer != null);
+ Assert.checkArgument(
+ mediaPlayer.getLastPlayedOrPlayingVoicemailUri().equals((voicemailUri)),
+ "the voicemail being played is the only voicemail that should"
+ + " be paused. last played voicemail:%s, uri:%s",
+ mediaPlayer.getLastPlayedOrPlayingVoicemailUri().toString(),
+ voicemailUri.toString());
+ Assert.checkArgument(
+ newVoicemailViewHolder.getViewHolderVoicemailUri().equals(voicemailUri),
+ "viewholder uri and mediaplayer view should be the same.");
+ newVoicemailViewHolderListener.pauseViewHolder(newVoicemailViewHolder);
+ }
+ };
+
private final View.OnClickListener playButtonListener =
- view -> playVoicemailWhenAvailableLocally();
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ LogUtil.i(
+ "NewVoicemailMediaPlayer.playButtonListener",
+ "play button for voicemailUri: %s",
+ voicemailUri.toString());
+ if (mediaPlayer.getLastPausedVoicemailUri() != null
+ && mediaPlayer
+ .getLastPausedVoicemailUri()
+ .toString()
+ .contentEquals(voicemailUri.toString())) {
+ LogUtil.i(
+ "NewVoicemailMediaPlayer.playButtonListener",
+ "resume playing voicemailUri: %s",
+ voicemailUri.toString());
+
+ newVoicemailViewHolderListener.resumePausedViewHolder(newVoicemailViewHolder);
+
+ } else {
+ playVoicemailWhenAvailableLocally();
+ }
+ }
+ };
/**
* Plays the voicemail when we are able to play the voicemail locally from the device. This
@@ -113,7 +312,7 @@ public class NewVoicemailMediaPlayerView extends LinearLayout {
Uri uri = contextUriPair.second;
try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
- if (cursor != null && cursor.moveToNext()) {
+ if (cursor != null && cursor.moveToFirst()) {
return new Pair<>(
cursor.getInt(cursor.getColumnIndex(VoicemailContract.Voicemails.HAS_CONTENT)) == 1,
uri);
@@ -137,17 +336,18 @@ public class NewVoicemailMediaPlayerView extends LinearLayout {
if (voicemailAvailableLocally) {
try {
- mediaPlayer = new MediaPlayer();
- mediaPlayer.setOnPreparedListener(onPreparedListener);
- mediaPlayer.setOnErrorListener(onErrorListener);
- mediaPlayer.setOnCompletionListener(onCompletionListener);
-
- mediaPlayer.reset();
- mediaPlayer.setDataSource(getContext(), uri);
-
- mediaPlayer.prepareAsync();
+ Assert.checkArgument(mediaPlayer != null, "media player should not have been null");
+ mediaPlayer.prepareMediaPlayerAndPlayVoicemailWhenReady(getContext(), uri);
} catch (Exception e) {
- LogUtil.e("NewVoicemailMediaPlayer.prepareMediaPlayer", "IOException " + e);
+ LogUtil.e(
+ "NewVoicemailMediaPlayer.prepareMediaPlayer",
+ "Exception when mediaPlayer.prepareMediaPlayerAndPlayVoicemailWhenReady"
+ + "(getContext(), uri)\n"
+ + e
+ + "\n uri:"
+ + uri
+ + "context should not be null, its value is :"
+ + getContext());
}
} else {
// TODO(a bug): Add logic for downloading voicemail content from the server.
@@ -189,57 +389,130 @@ public class NewVoicemailMediaPlayerView extends LinearLayout {
}
};
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- OnCompletionListener onCompletionListener =
- new OnCompletionListener() {
+ /**
+ * This is only called to update the media player view of the seekbar, and the duration and the
+ * play button. For constant updates the adapter should seek track. This is the state when a
+ * voicemail is playing.
+ */
+ public void updateSeekBarDurationAndShowPlayButton(NewVoicemailMediaPlayer mp) {
+ if (!mp.isPlaying()) {
+ return;
+ }
- @Override
- public void onCompletion(MediaPlayer mp) {
- LogUtil.i(
- "NewVoicemailMediaPlayer.onCompletionListener",
- "completed playing voicemailUri: %s",
- voicemailUri.toString());
- }
- };
+ playButton.setVisibility(GONE);
+ pauseButton.setVisibility(VISIBLE);
- private final OnPreparedListener onPreparedListener =
- new OnPreparedListener() {
+ Assert.checkArgument(
+ mp.equals(mediaPlayer), "there should only be one instance of a media player");
+ Assert.checkArgument(
+ mediaPlayer.getLastPreparedOrPreparingToPlayVoicemailUri().equals(voicemailUri));
+ Assert.checkArgument(mediaPlayer.getLastPlayedOrPlayingVoicemailUri().equals(voicemailUri));
+ Assert.isNotNull(mediaPlayer, "media player should have been set on bind");
+ Assert.checkArgument(mediaPlayer.isPlaying());
+ Assert.checkArgument(mediaPlayer.getCurrentPosition() >= 0);
+ Assert.checkArgument(mediaPlayer.getDuration() >= 0);
+ Assert.checkArgument(playButton.getVisibility() == GONE);
+ Assert.checkArgument(pauseButton.getVisibility() == VISIBLE);
+ Assert.checkArgument(seekBarView.getVisibility() == VISIBLE);
+ Assert.checkArgument(currentSeekBarPosition.getVisibility() == VISIBLE);
- @Override
- public void onPrepared(MediaPlayer mp) {
- LogUtil.i(
- "NewVoicemailMediaPlayer.onPreparedListener",
- "about to play voicemailUri: %s",
- voicemailUri.toString());
- mediaPlayer.start();
- }
- };
+ currentSeekBarPosition.setText(formatAsMinutesAndSeconds(mediaPlayer.getCurrentPosition()));
+ if (seekBarView.getMax() != mediaPlayer.getDuration()) {
+ seekBarView.setMax(mediaPlayer.getDuration());
+ }
+ seekBarView.setProgress(mediaPlayer.getCurrentPosition());
+ }
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- OnErrorListener onErrorListener =
- new OnErrorListener() {
- @Override
- public boolean onError(MediaPlayer mp, int what, int extra) {
- LogUtil.i(
- "NewVoicemailMediaPlayer.onErrorListener",
- "error playing voicemailUri: %s",
- voicemailUri.toString());
- return false;
- }
- };
+ /**
+ * What the default state of an expanded media player view should look like.
+ *
+ * @param currentlyExpandedViewHolderOnScreen
+ * @param mediaPlayer
+ */
+ public void setToResetState(
+ NewVoicemailViewHolder currentlyExpandedViewHolderOnScreen,
+ NewVoicemailMediaPlayer mediaPlayer) {
+ LogUtil.i(
+ "NewVoicemailMediaPlayer.setToResetState",
+ "update the seekbar for viewholder id:%d, mediaplayer view uri:%s, play button "
+ + "visible:%b, pause button visible:%b",
+ currentlyExpandedViewHolderOnScreen.getViewHolderId(),
+ String.valueOf(voicemailUri),
+ playButton.getVisibility() == VISIBLE,
+ pauseButton.getVisibility() == VISIBLE);
- void setFragmentManager(FragmentManager fragmentManager) {
- this.fragmentManager = fragmentManager;
+ if (playButton.getVisibility() == GONE) {
+ playButton.setVisibility(VISIBLE);
+ pauseButton.setVisibility(GONE);
+ }
+
+ Assert.checkArgument(playButton.getVisibility() == VISIBLE);
+ Assert.checkArgument(pauseButton.getVisibility() == GONE);
+
+ Assert.checkArgument(
+ !mediaPlayer.isPlaying(),
+ "when resetting an expanded " + "state, there should be no voicemail playing");
+
+ Assert.checkArgument(
+ mediaPlayer.getLastPlayedOrPlayingVoicemailUri().equals(Uri.EMPTY),
+ "reset should have been called before updating its media player view");
+ currentSeekBarPosition.setText(formatAsMinutesAndSeconds(0));
+ seekBarView.setProgress(0);
+ seekBarView.setMax(100);
}
- void setVoicemailEntryValues(VoicemailEntry voicemailEntry) {
- Assert.isNotNull(voicemailEntry);
- Uri uri = Uri.parse(voicemailEntry.voicemailUri());
- Assert.isNotNull(uri);
- Assert.isNotNull(totalDurationView);
+ public void setToPausedState(Uri toPausedState, NewVoicemailMediaPlayer mp) {
+ LogUtil.i(
+ "NewVoicemailMediaPlayer.setToPausedState",
+ "toPausedState uri:%s, play button visible:%b, pause button visible:%b",
+ toPausedState == null ? "null" : voicemailUri.toString(),
+ playButton.getVisibility() == VISIBLE,
+ pauseButton.getVisibility() == VISIBLE);
- voicemailUri = uri;
- totalDurationView.setText(
- VoicemailEntryText.getVoicemailDuration(getContext(), voicemailEntry));
+ playButton.setVisibility(VISIBLE);
+ pauseButton.setVisibility(GONE);
+
+ currentSeekBarPosition.setText(formatAsMinutesAndSeconds(mediaPlayer.getCurrentPosition()));
+ if (seekBarView.getMax() != mediaPlayer.getDuration()) {
+ seekBarView.setMax(mediaPlayer.getDuration());
+ }
+ seekBarView.setProgress(mediaPlayer.getCurrentPosition());
+
+ Assert.checkArgument(voicemailUri.equals(toPausedState));
+ Assert.checkArgument(!mp.isPlaying());
+ Assert.checkArgument(
+ mp.equals(mediaPlayer), "there should only be one instance of a media player");
+ Assert.checkArgument(
+ this.mediaPlayer.getLastPreparedOrPreparingToPlayVoicemailUri().equals(voicemailUri));
+ Assert.checkArgument(
+ this.mediaPlayer.getLastPlayedOrPlayingVoicemailUri().equals(voicemailUri));
+ Assert.checkArgument(this.mediaPlayer.getLastPausedVoicemailUri().equals(voicemailUri));
+ Assert.isNotNull(this.mediaPlayer, "media player should have been set on bind");
+ Assert.checkArgument(this.mediaPlayer.getCurrentPosition() >= 0);
+ Assert.checkArgument(this.mediaPlayer.getDuration() >= 0);
+ Assert.checkArgument(playButton.getVisibility() == VISIBLE);
+ Assert.checkArgument(pauseButton.getVisibility() == GONE);
+ Assert.checkArgument(seekBarView.getVisibility() == VISIBLE);
+ Assert.checkArgument(currentSeekBarPosition.getVisibility() == VISIBLE);
+ }
+
+ @NonNull
+ public Uri getVoicemailUri() {
+ return voicemailUri;
+ }
+
+ private String formatAsMinutesAndSeconds(int millis) {
+ int seconds = millis / 1000;
+ int minutes = seconds / 60;
+ seconds -= minutes * 60;
+ if (minutes > 99) {
+ minutes = 99;
+ }
+ return String.format(Locale.US, "%02d:%02d", minutes, seconds);
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ void setFragmentManager(FragmentManager fragmentManager) {
+ this.fragmentManager = fragmentManager;
}
}