summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/voicemail
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/voicemail')
-rw-r--r--java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java499
-rw-r--r--java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java188
-rw-r--r--java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java395
-rw-r--r--java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java258
-rw-r--r--java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml6
5 files changed, 1232 insertions, 114 deletions
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
index 1c53e3801..61fed52e6 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
@@ -17,15 +17,24 @@ package com.android.dialer.voicemail.listui;
import android.app.FragmentManager;
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.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.ThreadUtil;
import com.android.dialer.time.Clock;
import com.android.dialer.voicemail.listui.NewVoicemailViewHolder.NewVoicemailViewHolderListener;
import com.android.dialer.voicemail.model.VoicemailEntry;
+import java.util.Objects;
import java.util.Set;
/** {@link RecyclerView.Adapter} for the new voicemail call log fragment. */
@@ -38,8 +47,24 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<NewVoicemailViewHol
/** A valid id for {@link VoicemailEntry} is greater than 0 */
private int currentlyExpandedViewHolderId = -1;
- // A set of (re-usable) view holders being used by the recycler view to display voicemails
+ /**
+ * A set of (re-usable) view holders being used by the recycler view to display voicemails. This
+ * set may include multiple view holder with the same ID and shouldn't be used to lookup a
+ * specific viewholder based on this value, instead use newVoicemailViewHolderArrayMap for that
+ * purpose.
+ */
private final Set<NewVoicemailViewHolder> newVoicemailViewHolderSet = new ArraySet<>();
+ /**
+ * This allows us to retrieve the view holder corresponding to a particular view holder id, and
+ * will always ensure there is only (up-to-date) view holder corresponding to a view holder id,
+ * unlike the newVoicemailViewHolderSet.
+ */
+ private final ArrayMap<Integer, NewVoicemailViewHolder> newVoicemailViewHolderArrayMap =
+ new ArrayMap<>();
+
+ // A single instance of a media player re-used across the expanded view holders.
+ private final NewVoicemailMediaPlayer mediaPlayer =
+ new NewVoicemailMediaPlayer(new MediaPlayer());
/** @param cursor whose projection is {@link VoicemailCursorLoader.VOICEMAIL_COLUMNS} */
NewVoicemailAdapter(Cursor cursor, Clock clock, FragmentManager fragmentManager) {
@@ -47,11 +72,17 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<NewVoicemailViewHol
this.cursor = cursor;
this.clock = clock;
this.fragmentManager = fragmentManager;
+ initializeMediaPlayerListeners();
+ }
+
+ private void initializeMediaPlayerListeners() {
+ mediaPlayer.setOnCompletionListener(onCompletionListener);
+ mediaPlayer.setOnPreparedListener(onPreparedListener);
+ mediaPlayer.setOnErrorListener(onErrorListener);
}
@Override
public NewVoicemailViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
- LogUtil.enterBlock("NewVoicemailAdapter.onCreateViewHolder");
LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
View view = inflater.inflate(R.layout.new_voicemail_entry, viewGroup, false);
NewVoicemailViewHolder newVoicemailViewHolder = new NewVoicemailViewHolder(view, clock, this);
@@ -61,58 +92,464 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<NewVoicemailViewHol
@Override
public void onBindViewHolder(NewVoicemailViewHolder viewHolder, int position) {
+ // Remove if the viewholder is being recycled.
+ if (newVoicemailViewHolderArrayMap.containsKey(viewHolder.getViewHolderId())) {
+ // TODO(uabdullah): Remove the logging, only here for debugging during development.
+ LogUtil.i(
+ "NewVoicemailAdapter.onBindViewHolder",
+ "Removing from hashset:%d, hashsetSize:%d",
+ viewHolder.getViewHolderId(),
+ newVoicemailViewHolderArrayMap.size());
+
+ newVoicemailViewHolderArrayMap.remove(viewHolder.getViewHolderId());
+ }
+
+ viewHolder.reset();
cursor.moveToPosition(position);
- viewHolder.bind(cursor, fragmentManager);
- expandOrCollapseViewHolder(viewHolder);
+ viewHolder.bindViewHolderValuesFromAdapter(
+ cursor, fragmentManager, mediaPlayer, position, currentlyExpandedViewHolderId);
+
+ // Need this to ensure correct getCurrentlyExpandedViewHolder() value
+ newVoicemailViewHolderArrayMap.put(viewHolder.getViewHolderId(), viewHolder);
+
+ // If the viewholder is playing the voicemail, keep updating its media player view (seekbar,
+ // duration etc.)
+ if (viewHolder.isViewHolderExpanded() && mediaPlayer.isPlaying()) {
+ Assert.checkArgument(
+ viewHolder
+ .getViewHolderVoicemailUri()
+ .equals(mediaPlayer.getLastPlayedOrPlayingVoicemailUri()),
+ "only the expanded view holder can be playing.");
+ Assert.isNotNull(getCurrentlyExpandedViewHolder());
+ Assert.checkArgument(
+ getCurrentlyExpandedViewHolder()
+ .getViewHolderVoicemailUri()
+ .equals(mediaPlayer.getLastPlayedOrPlayingVoicemailUri()));
+
+ recursivelyUpdateMediaPlayerViewOfExpandedViewHolder(viewHolder);
+ }
+ // Updates the hashmap with the most up-to-date state of the viewholder.
+ newVoicemailViewHolderArrayMap.put(viewHolder.getViewHolderId(), viewHolder);
}
/**
- * Ensures a voicemail {@link NewVoicemailViewHolder} that was expanded and scrolled out of view,
- * doesn't have it's corresponding recycled view also expanded. It also ensures than when the
- * expanded voicemail is scrolled back into view, it still remains expanded.
+ * The {@link NewVoicemailAdapter} needs to keep track of {@link NewVoicemailViewHolder} that has
+ * been expanded. This is so that the adapter can ensure the correct {@link
+ * NewVoicemailMediaPlayerView} and {@link NewVoicemailViewHolder} states are maintained
+ * (playing/paused/reset) for the expanded viewholder, especially when views are recycled in
+ * {@link RecyclerView}. Since we can only have one expanded voicemail view holder, this method
+ * ensures that except for the currently expanded view holder, all the other view holders visible
+ * on the screen are collapsed.
+ *
+ * <p>The {@link NewVoicemailMediaPlayer} is also reset, if there is an existing playing
+ * voicemail.
+ *
+ * <p>This is the function that is responsible of keeping track of the expanded viewholder in the
+ * {@link NewVoicemailAdapter}
+ *
+ * <p>This is the first function called in the adapter when a viewholder has been expanded.
+ *
+ * <p>This is the function that is responsible of keeping track of the expanded viewholder in the
+ * {@link NewVoicemailAdapter}
*
- * @param viewHolder an {@link NewVoicemailViewHolder} that is either expanded or collapsed
+ * @param viewHolderRequestedToExpand is the view holder that is currently expanded.
+ * @param voicemailEntryOfViewHolder
*/
- private void expandOrCollapseViewHolder(NewVoicemailViewHolder viewHolder) {
- if (viewHolder.getViewHolderId() == currentlyExpandedViewHolderId) {
- viewHolder.expandViewHolder();
- } else {
- viewHolder.collapseViewHolder();
+ @Override
+ public void expandViewHolderFirstTimeAndCollapseAllOtherVisibleViewHolders(
+ NewVoicemailViewHolder viewHolderRequestedToExpand,
+ VoicemailEntry voicemailEntryOfViewHolder,
+ NewVoicemailViewHolderListener listener) {
+
+ LogUtil.i(
+ "NewVoicemailAdapter.expandViewHolderFirstTimeAndCollapseAllOtherVisibleViewHolders",
+ "viewholder id:%d being request to expand, isExpanded:%b, size of our view holder "
+ + "dataset:%d, hashmap size:%d",
+ viewHolderRequestedToExpand.getViewHolderId(),
+ viewHolderRequestedToExpand.isViewHolderExpanded(),
+ newVoicemailViewHolderSet.size(),
+ newVoicemailViewHolderArrayMap.size());
+
+ currentlyExpandedViewHolderId = viewHolderRequestedToExpand.getViewHolderId();
+
+ for (NewVoicemailViewHolder viewHolder : newVoicemailViewHolderSet) {
+ if (viewHolder.getViewHolderId() != viewHolderRequestedToExpand.getViewHolderId()) {
+ viewHolder.collapseViewHolder();
+ }
+ }
+
+ // If the media player is playing and we expand something other than the currently playing one
+ // we should stop playing the media player
+ if (mediaPlayer.isPlaying()
+ && !Objects.equals(
+ mediaPlayer.getLastPlayedOrPlayingVoicemailUri(),
+ viewHolderRequestedToExpand.getViewHolderVoicemailUri())) {
+ LogUtil.i(
+ "NewVoicemailAdapter.expandViewHolderFirstTimeAndCollapseAllOtherVisibleViewHolders",
+ "Reset the media player since we expanded something other that the playing "
+ + "voicemail, MP was playing:%s, viewholderExpanded:%d, MP.isPlaying():%b",
+ String.valueOf(mediaPlayer.getLastPlayedOrPlayingVoicemailUri()),
+ viewHolderRequestedToExpand.getViewHolderId(),
+ mediaPlayer.isPlaying());
+ mediaPlayer.reset();
+ }
+
+ // If the media player is paused and we expand something other than the currently paused one
+ // we should stop playing the media player
+ if (mediaPlayer.isPaused()
+ && !Objects.equals(
+ mediaPlayer.getLastPausedVoicemailUri(),
+ viewHolderRequestedToExpand.getViewHolderVoicemailUri())) {
+ LogUtil.i(
+ "NewVoicemailAdapter.expandViewHolderFirstTimeAndCollapseAllOtherVisibleViewHolders",
+ "There was an existing paused viewholder, the media player should reset since we "
+ + "expanded something other that the paused voicemail, MP.paused:%s",
+ String.valueOf(mediaPlayer.getLastPausedVoicemailUri()));
+ mediaPlayer.reset();
}
+
+ Assert.checkArgument(
+ !viewHolderRequestedToExpand.isViewHolderExpanded(),
+ "cannot expand a voicemail that is not collapsed");
+
+ viewHolderRequestedToExpand.expandAndBindViewHolderAndMediaPlayerViewWithAdapterValues(
+ voicemailEntryOfViewHolder, fragmentManager, mediaPlayer, listener);
+
+ // There should be nothing playing when we expand a viewholder for the first time
+ Assert.checkArgument(!mediaPlayer.isPlaying());
}
+ /**
+ * Ensures that when we collapse the expanded view, we don't expand it again when we are recycling
+ * the viewholders. If we collapse an existing playing voicemail viewholder, we should stop
+ * playing it.
+ *
+ * @param collapseViewHolder is the view holder that is currently collapsed.
+ */
@Override
- public int getItemCount() {
- return cursor.getCount();
+ public void collapseExpandedViewHolder(NewVoicemailViewHolder collapseViewHolder) {
+ Assert.checkArgument(collapseViewHolder.getViewHolderId() == currentlyExpandedViewHolderId);
+ collapseViewHolder.collapseViewHolder();
+ currentlyExpandedViewHolderId = -1;
+
+ // If the view holder is currently playing, then we should stop playing it.
+ if (mediaPlayer.isPlaying()) {
+ Assert.checkArgument(
+ Objects.equals(
+ mediaPlayer.getLastPlayedOrPlayingVoicemailUri(),
+ collapseViewHolder.getViewHolderVoicemailUri()),
+ "the voicemail being played should have been of the recently collapsed view holder.");
+ mediaPlayer.reset();
+ }
+ }
+
+ @Override
+ public void pauseViewHolder(NewVoicemailViewHolder expandedViewHolder) {
+ Assert.isNotNull(
+ getCurrentlyExpandedViewHolder(),
+ "cannot have pressed pause if the viewholder wasn't expanded");
+ Assert.checkArgument(
+ getCurrentlyExpandedViewHolder()
+ .getViewHolderVoicemailUri()
+ .equals(expandedViewHolder.getViewHolderVoicemailUri()),
+ "view holder whose pause button was pressed has to have been the expanded "
+ + "viewholder being tracked by the adapter.");
+ mediaPlayer.pauseMediaPlayer(expandedViewHolder.getViewHolderVoicemailUri());
+ expandedViewHolder.setPausedStateOfMediaPlayerView(
+ expandedViewHolder.getViewHolderVoicemailUri(), mediaPlayer);
+ }
+
+ @Override
+ public void resumePausedViewHolder(NewVoicemailViewHolder expandedViewHolder) {
+ Assert.isNotNull(
+ getCurrentlyExpandedViewHolder(),
+ "cannot have pressed pause if the viewholder wasn't expanded");
+ Assert.checkArgument(
+ getCurrentlyExpandedViewHolder()
+ .getViewHolderVoicemailUri()
+ .equals(expandedViewHolder.getViewHolderVoicemailUri()),
+ "view holder whose play button was pressed has to have been the expanded "
+ + "viewholder being tracked by the adapter.");
+ Assert.isNotNull(
+ mediaPlayer.getLastPausedVoicemailUri(), "there should be be an pausedUri to resume");
+ Assert.checkArgument(
+ mediaPlayer
+ .getLastPlayedOrPlayingVoicemailUri()
+ .equals(expandedViewHolder.getViewHolderVoicemailUri()),
+ "only the last playing uri can be resumed");
+ Assert.checkArgument(
+ mediaPlayer
+ .getLastPreparedOrPreparingToPlayVoicemailUri()
+ .equals(expandedViewHolder.getViewHolderVoicemailUri()),
+ "only the last prepared uri can be resumed");
+ Assert.checkArgument(
+ mediaPlayer
+ .getLastPreparedOrPreparingToPlayVoicemailUri()
+ .equals(mediaPlayer.getLastPlayedOrPlayingVoicemailUri()),
+ "the last prepared and playing voicemails have to be the same when resuming");
+
+ onPreparedListener.onPrepared(mediaPlayer.getMediaPlayer());
}
/**
- * We can only have one expanded voicemail view holder. This allows us to ensure that except for
- * the currently expanded view holder, all the other view holders visible on the screen are
- * collapsed.
+ * This function is called recursively to update the seekbar, duration, play/pause buttons of the
+ * expanded view holder if its playing.
+ *
+ * <p>Since this function is called at 30 frames/second, its possible (and eventually will happen)
+ * that between each update the playing voicemail state could have changed, in which case this
+ * method should stop calling itself. These conditions are:
*
- * @param expandedViewHolder is the view holder that is currently expanded.
+ * <ul>
+ * <li>The user scrolled the playing voicemail out of view.
+ * <li>Another view holder was expanded.
+ * <li>The playing voicemail was paused.
+ * <li>The media player returned {@link MediaPlayer#isPlaying()} to be true but had its {@link
+ * MediaPlayer#getCurrentPosition()} > {@link MediaPlayer#getDuration()}.
+ * <li>The {@link MediaPlayer} stopped playing.
+ * </ul>
+ *
+ * <p>Note: Since the update happens at 30 frames/second, it's also possible that the viewholder
+ * was recycled when scrolling the playing voicemail out of view.
+ *
+ * @param expandedViewHolderPossiblyPlaying the view holder that was expanded and could or could
+ * not be playing. This viewholder can be recycled.
*/
- @Override
- public void onViewHolderExpanded(NewVoicemailViewHolder expandedViewHolder) {
- currentlyExpandedViewHolderId = expandedViewHolder.getViewHolderId();
+ private void recursivelyUpdateMediaPlayerViewOfExpandedViewHolder(
+ NewVoicemailViewHolder expandedViewHolderPossiblyPlaying) {
+
+ // It's possible that by the time this is run, the expanded view holder has been
+ // scrolled out of view (and possibly recycled)
+ if (getCurrentlyExpandedViewHolder() == null) {
+ LogUtil.i(
+ "NewVoicemailAdapter.recursivelyUpdateMediaPlayerViewOfExpandedViewHolder",
+ "viewholder:%d media player view, no longer on screen, no need to update",
+ expandedViewHolderPossiblyPlaying.getViewHolderId());
+ return;
+ }
+
+ // Another viewholder was expanded, no need to update
+ if (!getCurrentlyExpandedViewHolder().equals(expandedViewHolderPossiblyPlaying)) {
+ LogUtil.i(
+ "NewVoicemailAdapter.recursivelyUpdateMediaPlayerViewOfExpandedViewHolder",
+ "currentlyExpandedViewHolderId:%d and the one we are attempting to update:%d "
+ + "aren't the same.",
+ currentlyExpandedViewHolderId,
+ expandedViewHolderPossiblyPlaying.getViewHolderId());
+ return;
+ }
+
+ Assert.checkArgument(expandedViewHolderPossiblyPlaying.isViewHolderExpanded());
+ Assert.checkArgument(
+ expandedViewHolderPossiblyPlaying.getViewHolderId()
+ == getCurrentlyExpandedViewHolder().getViewHolderId());
+
+ // If the viewholder was paused, there is no need to update the media player view
+ if (mediaPlayer.isPaused()) {
+ Assert.checkArgument(
+ expandedViewHolderPossiblyPlaying
+ .getViewHolderVoicemailUri()
+ .equals(mediaPlayer.getLastPausedVoicemailUri()),
+ "only the expanded viewholder can be paused.");
+
+ LogUtil.i(
+ "NewVoicemailAdapter.recursivelyUpdateMediaPlayerViewOfExpandedViewHolder",
+ "set the media player to a paused state");
+ expandedViewHolderPossiblyPlaying.setPausedStateOfMediaPlayerView(
+ expandedViewHolderPossiblyPlaying.getViewHolderVoicemailUri(), mediaPlayer);
+ return;
+ }
+
+ // In some weird corner cases a media player could return isPlaying() as true but would
+ // have getCurrentPosition > getDuration(). We consider that as the voicemail has finished
+ // playing.
+ if (mediaPlayer.isPlaying() && mediaPlayer.getCurrentPosition() < mediaPlayer.getDuration()) {
+
+ Assert.checkArgument(
+ mediaPlayer
+ .getLastPlayedOrPlayingVoicemailUri()
+ .equals(getCurrentlyExpandedViewHolder().getViewHolderVoicemailUri()));
+ // TODO(uabdullah): Remove this, here for debugging during development.
+ LogUtil.i(
+ "NewVoicemailAdapter.recursivelyUpdateMediaPlayerViewOfExpandedViewHolder",
+ "recursely update the player, currentlyExpanded:%d",
+ expandedViewHolderPossiblyPlaying.getViewHolderId());
+
+ Assert.checkArgument(
+ expandedViewHolderPossiblyPlaying
+ .getViewHolderVoicemailUri()
+ .equals(getCurrentlyExpandedViewHolder().getViewHolderVoicemailUri()));
+
+ expandedViewHolderPossiblyPlaying.updateMediaPlayerViewWithPlayingState(
+ expandedViewHolderPossiblyPlaying, mediaPlayer);
+
+ ThreadUtil.postDelayedOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ recursivelyUpdateMediaPlayerViewOfExpandedViewHolder(
+ expandedViewHolderPossiblyPlaying);
+ }
+ },
+ 1000 / 30 /*30 FPS*/);
+ return;
+ }
+
+ if (!mediaPlayer.isPlaying()
+ || (mediaPlayer.isPlaying()
+ && mediaPlayer.getCurrentPosition() > mediaPlayer.getDuration())) {
+ LogUtil.i(
+ "NewVoicemailAdapter.recursivelyUpdateMediaPlayerViewOfExpandedViewHolder",
+ "resetting the player, currentlyExpanded:%d, MPPlaying:%b",
+ getCurrentlyExpandedViewHolder().getViewHolderId(),
+ mediaPlayer.isPlaying());
+ mediaPlayer.reset();
+ Assert.checkArgument(
+ expandedViewHolderPossiblyPlaying
+ .getViewHolderVoicemailUri()
+ .equals(getCurrentlyExpandedViewHolder().getViewHolderVoicemailUri()));
+ expandedViewHolderPossiblyPlaying.setMediaPlayerViewToResetState(
+ expandedViewHolderPossiblyPlaying, mediaPlayer);
+ return;
+ }
+
+ String error =
+ String.format(
+ "expandedViewHolderPossiblyPlaying:%d, expanded:%b, CurrentExpanded:%d, uri:%s, "
+ + "MPPlaying:%b, MPPaused:%b, MPPreparedUri:%s, MPPausedUri:%s",
+ expandedViewHolderPossiblyPlaying.getViewHolderId(),
+ expandedViewHolderPossiblyPlaying.isViewHolderExpanded(),
+ currentlyExpandedViewHolderId,
+ String.valueOf(expandedViewHolderPossiblyPlaying.getViewHolderVoicemailUri()),
+ mediaPlayer.isPlaying(),
+ mediaPlayer.isPaused(),
+ String.valueOf(mediaPlayer.getLastPreparedOrPreparingToPlayVoicemailUri()),
+ String.valueOf(mediaPlayer.getLastPreparedOrPreparingToPlayVoicemailUri()));
+
+ throw Assert.createAssertionFailException(
+ "All cases should have been handled before. Error " + error);
+ }
+
+ // When a voicemail has finished playing.
+ OnCompletionListener onCompletionListener =
+ new OnCompletionListener() {
+
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ Assert.checkArgument(
+ mediaPlayer
+ .getLastPlayedOrPlayingVoicemailUri()
+ .equals(mediaPlayer.getLastPreparedOrPreparingToPlayVoicemailUri()));
+ Assert.checkArgument(!mediaPlayer.isPlaying());
+
+ LogUtil.i(
+ "NewVoicemailAdapter.onCompletionListener",
+ "completed playing voicemailUri: %s, expanded viewholder is %d, visibility :%b",
+ mediaPlayer.getLastPlayedOrPlayingVoicemailUri().toString(),
+ currentlyExpandedViewHolderId,
+ isCurrentlyExpandedViewHolderInViewHolderSet());
+
+ Assert.checkArgument(
+ currentlyExpandedViewHolderId != -1,
+ "a voicemail that was never expanded, should never be playing.");
+ mediaPlayer.reset();
+ }
+ };
+
+ // When a voicemail has been prepared and can be played
+ private final OnPreparedListener onPreparedListener =
+ new OnPreparedListener() {
+
+ /**
+ * When a user pressed the play button, this listener should be called immediately. The
+ * asserts ensures that is the case. This function starts playing the voicemail and updates
+ * the UI.
+ */
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ LogUtil.i(
+ "NewVoicemailAdapter.onPrepared",
+ "MPPreparedUri: %s, currentlyExpandedViewHolderId:%d, and its visibility on "
+ + "the screen is:%b",
+ String.valueOf(mediaPlayer.getLastPreparedOrPreparingToPlayVoicemailUri()),
+ currentlyExpandedViewHolderId,
+ isCurrentlyExpandedViewHolderInViewHolderSet());
+
+ NewVoicemailViewHolder currentlyExpandedViewHolder = getCurrentlyExpandedViewHolder();
+ Assert.checkArgument(currentlyExpandedViewHolder != null);
+ Assert.checkArgument(
+ currentlyExpandedViewHolder
+ .getViewHolderVoicemailUri()
+ .equals(mediaPlayer.getLastPreparedOrPreparingToPlayVoicemailUri()),
+ "should only have prepared the last expanded view holder.");
+
+ mediaPlayer.start(mediaPlayer.getLastPreparedOrPreparingToPlayVoicemailUri());
+
+ recursivelyUpdateMediaPlayerViewOfExpandedViewHolder(currentlyExpandedViewHolder);
+
+ Assert.checkArgument(mediaPlayer.isPlaying());
+ LogUtil.i("NewVoicemailAdapter.onPrepared", "voicemail should be playing");
+ }
+ };
+
+ // TODO(uabdullah): when playing the voicemail results in an error
+ // we must update the viewholder and mention there was an error playing the voicemail, and reset
+ // the media player and the media player view
+ private final OnErrorListener onErrorListener =
+ new OnErrorListener() {
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ Assert.checkArgument(
+ mediaPlayer.getMediaPlayer().equals(mp),
+ "there should always only be one instance of the media player");
+ Assert.checkArgument(
+ mediaPlayer
+ .getLastPlayedOrPlayingVoicemailUri()
+ .equals(mediaPlayer.getLastPreparedOrPreparingToPlayVoicemailUri()));
+ LogUtil.i(
+ "NewVoicemailAdapter.onErrorListener",
+ "error playing voicemailUri: %s",
+ mediaPlayer.getLastPlayedOrPlayingVoicemailUri().toString());
+ return false;
+ }
+ };
+
+ private boolean isCurrentlyExpandedViewHolderInViewHolderSet() {
for (NewVoicemailViewHolder viewHolder : newVoicemailViewHolderSet) {
- if (!viewHolder.equals(expandedViewHolder)) {
- viewHolder.collapseViewHolder();
+ if (viewHolder.getViewHolderId() == currentlyExpandedViewHolderId) {
+ return true;
}
}
+ return false;
}
/**
- * Ensures that when we collapse the expanded view, we don't expand it again when we are recycling
- * the viewholders.
+ * The expanded view holder may or may not be visible on the screen. Since the {@link
+ * NewVoicemailViewHolder} may be recycled, it's possible that the expanded view holder is
+ * recycled for a non-expanded view holder when the expanded view holder is scrolled out of view.
*
- * @param collapseViewHolder is the view holder that is currently collapsed.
+ * @return the expanded view holder if it is amongst the recycled views on the screen, otherwise
+ * null.
*/
- @Override
- public void onViewHolderCollapsed(NewVoicemailViewHolder collapseViewHolder) {
- if (collapseViewHolder.getViewHolderId() == currentlyExpandedViewHolderId) {
- currentlyExpandedViewHolderId = -1;
+ @Nullable
+ private NewVoicemailViewHolder getCurrentlyExpandedViewHolder() {
+ if (newVoicemailViewHolderArrayMap.containsKey(currentlyExpandedViewHolderId)) {
+ Assert.checkArgument(
+ newVoicemailViewHolderArrayMap.get(currentlyExpandedViewHolderId).getViewHolderId()
+ == currentlyExpandedViewHolderId);
+ return newVoicemailViewHolderArrayMap.get(currentlyExpandedViewHolderId);
+ } else {
+ // returned when currentlyExpandedViewHolderId = -1 (viewholder was collapsed)
+ LogUtil.i(
+ "NewVoicemailAdapter.getCurrentlyExpandedViewHolder",
+ "no view holder found in newVoicemailViewHolderArrayMap size:%d for %d",
+ newVoicemailViewHolderArrayMap.size(),
+ currentlyExpandedViewHolderId);
+ return null;
}
}
+
+ @Override
+ public int getItemCount() {
+ return cursor.getCount();
+ }
}
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java
new file mode 100644
index 000000000..2d59b241b
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.voicemail.listui;
+
+import android.content.Context;
+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.support.annotation.NonNull;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import java.io.IOException;
+
+/** A wrapper around {@link MediaPlayer} */
+public class NewVoicemailMediaPlayer {
+
+ private final MediaPlayer mediaPlayer;
+ private Uri voicemailLastPlayedOrPlayingUri;
+ private Uri voicemailUriLastPreparedOrPreparingToPlay;
+
+ private OnErrorListener newVoicemailMediaPlayerOnErrorListener;
+ private OnPreparedListener newVoicemailMediaPlayerOnPreparedListener;
+ private OnCompletionListener newVoicemailMediaPlayerOnCompletionListener;
+ private Uri pausedUri;
+
+ public NewVoicemailMediaPlayer(@NonNull MediaPlayer player) {
+ mediaPlayer = Assert.isNotNull(player);
+ }
+
+ public void prepareMediaPlayerAndPlayVoicemailWhenReady(Context context, Uri uri)
+ throws IOException {
+ Assert.checkArgument(uri != null, "Media player cannot play a null uri");
+ LogUtil.i(
+ "NewVoicemailMediaPlayer",
+ "trying to prepare playing voicemail uri: %s",
+ String.valueOf(uri));
+ try {
+ reset();
+ voicemailUriLastPreparedOrPreparingToPlay = uri;
+ verifyListenersNotNull();
+ LogUtil.i("NewVoicemailMediaPlayer", "setData source");
+ mediaPlayer.setDataSource(context, uri);
+ LogUtil.i("NewVoicemailMediaPlayer", "prepare async");
+ mediaPlayer.prepareAsync();
+ } catch (IllegalStateException e) {
+ LogUtil.i(
+ "NewVoicemailMediaPlayer", "caught an IllegalStateException state exception : \n" + e);
+ } catch (Exception e) {
+ LogUtil.i(
+ "NewVoicemailMediaPlayer",
+ "threw an Exception " + e + " for uri: " + uri + "for context : " + context);
+ }
+ }
+
+ private void verifyListenersNotNull() {
+ Assert.isNotNull(
+ newVoicemailMediaPlayerOnErrorListener,
+ "newVoicemailMediaPlayerOnErrorListener must be set before preparing to "
+ + "play voicemails");
+ Assert.isNotNull(
+ newVoicemailMediaPlayerOnCompletionListener,
+ "newVoicemailMediaPlayerOnCompletionListener must be set before preparing"
+ + " to play voicemails");
+ Assert.isNotNull(
+ newVoicemailMediaPlayerOnPreparedListener,
+ "newVoicemailMediaPlayerOnPreparedListener must be set before preparing to"
+ + " play voicemails");
+ }
+
+ // Must be called from onPrepared
+ public void start(Uri startPlayingVoicemailUri) {
+ Assert.checkArgument(
+ startPlayingVoicemailUri.equals(voicemailUriLastPreparedOrPreparingToPlay),
+ "uri:%s was not prepared before calling start. Uri that is currently prepared: %s",
+ startPlayingVoicemailUri,
+ getLastPreparedOrPreparingToPlayVoicemailUri());
+
+ mediaPlayer.start();
+ voicemailLastPlayedOrPlayingUri = startPlayingVoicemailUri;
+ pausedUri = null;
+ }
+
+ public void reset() {
+ LogUtil.enterBlock("NewVoicemailMediaPlayer.reset");
+ mediaPlayer.reset();
+ voicemailLastPlayedOrPlayingUri = null;
+ voicemailUriLastPreparedOrPreparingToPlay = null;
+ pausedUri = null;
+ }
+
+ public void pauseMediaPlayer(Uri voicemailUri) {
+ pausedUri = voicemailUri;
+ Assert.checkArgument(
+ voicemailUriLastPreparedOrPreparingToPlay.equals(voicemailLastPlayedOrPlayingUri),
+ "last prepared and last playing should be the same");
+ Assert.checkArgument(
+ pausedUri.equals(voicemailLastPlayedOrPlayingUri),
+ "only the last played uri can be paused");
+ mediaPlayer.pause();
+ }
+
+ public void seekTo(int progress) {
+ mediaPlayer.seekTo(progress);
+ }
+
+ public void setOnErrorListener(OnErrorListener onErrorListener) {
+ mediaPlayer.setOnErrorListener(onErrorListener);
+ newVoicemailMediaPlayerOnErrorListener = onErrorListener;
+ }
+
+ public void setOnPreparedListener(OnPreparedListener onPreparedListener) {
+ mediaPlayer.setOnPreparedListener(onPreparedListener);
+ newVoicemailMediaPlayerOnPreparedListener = onPreparedListener;
+ }
+
+ public void setOnCompletionListener(OnCompletionListener onCompletionListener) {
+ mediaPlayer.setOnCompletionListener(onCompletionListener);
+ newVoicemailMediaPlayerOnCompletionListener = onCompletionListener;
+ }
+
+ /**
+ * Note: In some cases it's possible mediaPlayer.isPlaying() can return true, but
+ * mediaPlayer.getCurrentPosition() can be greater than mediaPlayer.getDuration(), after which
+ * mediaPlayer.isPlaying() will be false. This is a weird corner case and adding the
+ * mediaPlayer.getCurrentPosition() < mediaPlayer.getDuration() check here messes with the
+ * mediaPlayer.start() (doesn't return mediaPlayer.isPlaying() to be true immediately).
+ *
+ * @return if the media plaer;
+ */
+ public boolean isPlaying() {
+ return mediaPlayer.isPlaying();
+ }
+
+ public int getCurrentPosition() {
+ return mediaPlayer.getCurrentPosition();
+ }
+
+ public Uri getLastPlayedOrPlayingVoicemailUri() {
+ if (mediaPlayer.isPlaying()) {
+ Assert.isNotNull(voicemailLastPlayedOrPlayingUri);
+ }
+
+ return voicemailLastPlayedOrPlayingUri == null ? Uri.EMPTY : voicemailLastPlayedOrPlayingUri;
+ }
+
+ /**
+ * All the places that call this function, we expect the voicemail to have been prepared, but we
+ * could get rid of the assert check in the future if needed.
+ */
+ public Uri getLastPreparedOrPreparingToPlayVoicemailUri() {
+ return Assert.isNotNull(
+ voicemailUriLastPreparedOrPreparingToPlay,
+ "we expect whoever called this to have prepared a voicemail before calling this function");
+ }
+
+ public Uri getLastPausedVoicemailUri() {
+ return pausedUri;
+ }
+
+ public MediaPlayer getMediaPlayer() {
+ return mediaPlayer;
+ }
+
+ public int getDuration() {
+ Assert.checkArgument(mediaPlayer != null);
+ return mediaPlayer.getDuration();
+ }
+
+ public boolean isPaused() {
+ return pausedUri != null;
+ }
+}
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;
}
}
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
index f08e6bfd7..02b05db03 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
@@ -15,17 +15,22 @@
*/
package com.android.dialer.voicemail.listui;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
import android.app.FragmentManager;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
-import android.support.annotation.VisibleForTesting;
+import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.QuickContactBadge;
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.contactphoto.ContactPhotoManager;
import com.android.dialer.lettertile.LetterTileDrawable;
@@ -44,6 +49,8 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On
private final Clock clock;
private boolean isViewHolderExpanded;
private int viewHolderId;
+ private VoicemailEntry voicemailEntryOfViewHolder;
+ @NonNull private Uri viewHolderVoicemailUri;
private final NewVoicemailViewHolderListener voicemailViewHolderListener;
NewVoicemailViewHolder(
@@ -58,19 +65,45 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On
mediaPlayerView = view.findViewById(R.id.new_voicemail_media_player);
this.clock = clock;
voicemailViewHolderListener = newVoicemailViewHolderListener;
+
+ viewHolderId = -1;
+ isViewHolderExpanded = false;
+ viewHolderVoicemailUri = null;
}
- void bind(Cursor cursor, FragmentManager fragmentManager) {
- VoicemailEntry voicemailEntry = VoicemailCursorLoader.toVoicemailEntry(cursor);
- viewHolderId = voicemailEntry.id();
- primaryTextView.setText(VoicemailEntryText.buildPrimaryVoicemailText(context, voicemailEntry));
+ /**
+ * When the {@link RecyclerView} displays voicemail entries, it might recycle the views upon
+ * scrolling. In that case we need to ensure that the member variables of this {@link
+ * NewVoicemailViewHolder} and its views are correctly set, especially when this {@link
+ * NewVoicemailViewHolder} is recycled.
+ *
+ * @param cursor the voicemail data from {@link AnnotatedCallLog} generated by the {@link
+ * VoicemailCursorLoader} related
+ * @param fragmentManager FragmentManager retrieved from {@link
+ * NewVoicemailFragment#getActivity()}
+ * @param mediaPlayer
+ * @param position the position of the item within the adapter's data set.
+ * @param currentlyExpandedViewHolderId the value the adapter keeps track of which viewholder if
+ */
+ void bindViewHolderValuesFromAdapter(
+ Cursor cursor,
+ FragmentManager fragmentManager,
+ NewVoicemailMediaPlayer mediaPlayer,
+ int position,
+ int currentlyExpandedViewHolderId) {
+
+ voicemailEntryOfViewHolder = VoicemailCursorLoader.toVoicemailEntry(cursor);
+ viewHolderId = voicemailEntryOfViewHolder.id();
+ viewHolderVoicemailUri = Uri.parse(voicemailEntryOfViewHolder.voicemailUri());
+ primaryTextView.setText(
+ VoicemailEntryText.buildPrimaryVoicemailText(context, voicemailEntryOfViewHolder));
secondaryTextView.setText(
- VoicemailEntryText.buildSecondaryVoicemailText(context, clock, voicemailEntry));
+ VoicemailEntryText.buildSecondaryVoicemailText(context, clock, voicemailEntryOfViewHolder));
- String voicemailTranscription = voicemailEntry.transcription();
+ String voicemailTranscription = voicemailEntryOfViewHolder.transcription();
if (TextUtils.isEmpty(voicemailTranscription)) {
- transcriptionTextView.setVisibility(View.GONE);
+ transcriptionTextView.setVisibility(GONE);
transcriptionTextView.setText(null);
} else {
transcriptionTextView.setVisibility(View.VISIBLE);
@@ -78,9 +111,49 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On
}
itemView.setOnClickListener(this);
- setPhoto(voicemailEntry);
- mediaPlayerView.setVoicemailEntryValues(voicemailEntry);
- mediaPlayerView.setFragmentManager(fragmentManager);
+ setPhoto(voicemailEntryOfViewHolder);
+
+ // Update the expanded/collapsed state of this view holder
+ // Only update the binding of the mediaPlayerView of the expanded view holder
+ if (viewHolderId == currentlyExpandedViewHolderId) {
+ LogUtil.i(
+ "NewVoicemailViewHolder.bindViewHolderValuesFromAdapter",
+ "viewHolderId:%d is expanded, update its mediaplayer view",
+ viewHolderId);
+ expandAndBindViewHolderAndMediaPlayerViewWithAdapterValues(
+ voicemailEntryOfViewHolder, fragmentManager, mediaPlayer, voicemailViewHolderListener);
+ LogUtil.i(
+ "NewVoicemailViewHolder.bindViewHolderValuesFromAdapter",
+ "After 2nd updating the MPPlayerView: viewHolderId:%d, uri:%s, MediaplayerView(after "
+ + "updated):%s, adapter position passed down:%d, getAdapterPos:%d",
+ viewHolderId,
+ String.valueOf(viewHolderVoicemailUri),
+ String.valueOf(mediaPlayerView.getVoicemailUri()),
+ position,
+ getAdapterPosition());
+ Assert.checkArgument(
+ mediaPlayerView.getVisibility() == VISIBLE,
+ "a expanded viewholder should have its media player view visible");
+ } else {
+ LogUtil.i(
+ "NewVoicemailViewHolder.bindViewHolderValuesFromAdapter",
+ "viewHolderId:%d is not the expanded one, collapse it and don't update the MpView",
+ viewHolderId);
+ collapseViewHolder();
+ Assert.checkArgument(
+ mediaPlayerView.getVisibility() == GONE,
+ "a collapsed viewholder should not have its media player view visible");
+ }
+ LogUtil.i(
+ "NewVoicemailViewHolder.bindViewHolderValuesFromAdapter",
+ "Final value after updating: viewHolderId:%d, uri:%s, MediaplayerView(not updated):%s,"
+ + " adapter position passed down:%d, getAdapterPos:%d, MPPlayerVisibility:%b",
+ viewHolderId,
+ String.valueOf(viewHolderVoicemailUri),
+ String.valueOf(mediaPlayerView.getVoicemailUri()),
+ position,
+ getAdapterPosition(),
+ mediaPlayerView.getVisibility() == VISIBLE);
}
// TODO(uabdullah): Consider/Implement TYPE (e.g Spam, TYPE_VOICEMAIL)
@@ -96,19 +169,148 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On
}
void collapseViewHolder() {
+ LogUtil.i(
+ "NewVoicemailViewHolder.collapseViewHolder",
+ "viewHolderId:%d is being collapsed, its MPViewUri:%s, its Uri is :%s",
+ viewHolderId,
+ String.valueOf(mediaPlayerView.getVoicemailUri()),
+ String.valueOf(viewHolderVoicemailUri));
transcriptionTextView.setMaxLines(1);
isViewHolderExpanded = false;
- mediaPlayerView.setVisibility(View.GONE);
+ mediaPlayerView.setVisibility(GONE);
+ }
+
+ // When we are recycling the views ensure that we reset the viewHolder, as if its brand new
+ public void reset() {
+ LogUtil.i(
+ "NewVoicemailViewHolder.reset()",
+ "Reset the viewholder, currently viewHolderId:%d, uri:%s, isViewHolderExpanded:%b, "
+ + "its MediaPlayerViewUri:%s",
+ viewHolderId,
+ String.valueOf(viewHolderVoicemailUri),
+ isViewHolderExpanded,
+ String.valueOf(mediaPlayerView.getVoicemailUri()));
+
+ viewHolderId = -1;
+ isViewHolderExpanded = false;
+ viewHolderVoicemailUri = null;
+
+ mediaPlayerView.reset();
+
+ LogUtil.i(
+ "NewVoicemailViewHolder.reset()",
+ "Reset the viewholder, after resetting viewHolderId:%d, uri:%s, isViewHolderExpanded:%b",
+ viewHolderId,
+ String.valueOf(viewHolderVoicemailUri),
+ isViewHolderExpanded);
}
- void expandViewHolder() {
- LogUtil.i("NewVoicemailViewHolder.expandViewHolder", "voicemail id: %d", viewHolderId);
+ /**
+ * Is only called when a user either clicks a {@link NewVoicemailViewHolder} to expand it or if
+ * the user had already expanded, then scrolled the {@link NewVoicemailViewHolder} out of view and
+ * then scrolled it back into view, and during the binding (as the views are recyled in {@link
+ * RecyclerView}) we restore the expanded state of the {@link NewVoicemailViewHolder}.
+ *
+ * <p>This function also tracks if the state of this viewholder is expanded.
+ *
+ * @param voicemailEntry are the voicemail related values from the {@link AnnotatedCallLog}
+ * @param fragmentManager FragmentManager retrieved from {@link
+ * NewVoicemailFragment#getActivity()}
+ * @param mediaPlayer there should only be one instance of this passed down from the {@link
+ * NewVoicemailAdapter}
+ * @param voicemailViewHolderListener
+ */
+ void expandAndBindViewHolderAndMediaPlayerViewWithAdapterValues(
+ VoicemailEntry voicemailEntry,
+ FragmentManager fragmentManager,
+ NewVoicemailMediaPlayer mediaPlayer,
+ NewVoicemailViewHolderListener voicemailViewHolderListener) {
+
+ Assert.isNotNull(voicemailViewHolderListener);
+ Assert.checkArgument(
+ voicemailEntry.id() == viewHolderId, "ensure that the adapter binding has taken place");
+ Assert.checkArgument(
+ Uri.parse(voicemailEntry.voicemailUri()).equals(viewHolderVoicemailUri),
+ "ensure that the adapter binding has taken place");
+ LogUtil.i(
+ "NewVoicemailViewHolder.expandAndBindViewHolderAndMediaPlayerViewWithAdapterValues",
+ "voicemail id: %d, value of isViewHolderExpanded:%b, before setting it to be true, and"
+ + " value of ViewholderUri:%s, MPView:%s, before updating it",
+ viewHolderId,
+ isViewHolderExpanded,
+ String.valueOf(viewHolderVoicemailUri),
+ String.valueOf(mediaPlayerView.getVoicemailUri()));
+
transcriptionTextView.setMaxLines(999);
isViewHolderExpanded = true;
+ // Once the media player is visible update its state
mediaPlayerView.setVisibility(View.VISIBLE);
+ mediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView(
+ this, voicemailEntry, fragmentManager, mediaPlayer, voicemailViewHolderListener);
+ LogUtil.i(
+ "NewVoicemailViewHolder.expandAndBindViewHolderAndMediaPlayerViewWithAdapterValues",
+ "voicemail id: %d, value of isViewHolderExpanded:%b, after setting it to be true, and"
+ + " value of ViewholderUri:%s, MPView:%s, after updating it",
+ viewHolderId,
+ isViewHolderExpanded,
+ String.valueOf(viewHolderVoicemailUri),
+ String.valueOf(mediaPlayerView.getVoicemailUri()));
+ }
+
+ /**
+ * Called when we want to update the voicemail that is currently playing Updates the Seekbar,
+ * duration timer and the play/pause button visibility when the expanded voicemail is being
+ * played.
+ */
+ public void updateMediaPlayerViewWithPlayingState(
+ NewVoicemailViewHolder newVoicemailViewHolder, NewVoicemailMediaPlayer mp) {
+
+ LogUtil.i(
+ "NewVoicemailViewHolder.updateMediaPlayerViewWithPlayingState",
+ "viewholderUri:%s, mediaPlayerViewUri:%s, MPPosition:%d, MpDuration:%d, MpIsPlaying:%b",
+ newVoicemailViewHolder.getViewHolderVoicemailUri().toString(),
+ mediaPlayerView.getVoicemailUri().toString(),
+ mp.getCurrentPosition(),
+ mp.getDuration(),
+ mp.isPlaying());
+
+ Assert.checkArgument(
+ mp.isPlaying(),
+ "this method is only called when we are certain that the media player is playing");
+
+ LogUtil.i(
+ "NewVoicemailViewHolder.updateMediaPlayerViewWithPlayingState",
+ "viewholderUri:%s, mediaPlayerViewUri:%s",
+ newVoicemailViewHolder.getViewHolderVoicemailUri().toString(),
+ mediaPlayerView.getVoicemailUri().toString());
+ Assert.checkArgument(
+ newVoicemailViewHolder
+ .getViewHolderVoicemailUri()
+ .equals(mediaPlayerView.getVoicemailUri()),
+ "the mediaplayer view must be that of the viewholder we are updating");
+ Assert.checkArgument(
+ mp.getLastPlayedOrPlayingVoicemailUri()
+ .equals(mp.getLastPreparedOrPreparingToPlayVoicemailUri()),
+ "the media player view we are attempting to update should be of the "
+ + "currently prepared and playing voicemail");
+
+ mediaPlayerView.updateSeekBarDurationAndShowPlayButton(mp);
+ }
+
+ public void setMediaPlayerViewToResetState(
+ NewVoicemailViewHolder currentlyExpandedViewHolderOnScreen,
+ NewVoicemailMediaPlayer mediaPlayer) {
+ Assert.isNotNull(currentlyExpandedViewHolderOnScreen);
+ mediaPlayerView.setToResetState(currentlyExpandedViewHolderOnScreen, mediaPlayer);
+ }
+
+ public void setPausedStateOfMediaPlayerView(Uri uri, NewVoicemailMediaPlayer mediaPlayer) {
+ Assert.checkArgument(viewHolderVoicemailUri.equals(uri));
+ Assert.checkArgument(mediaPlayerView.getVoicemailUri().equals(uri));
+ Assert.checkArgument(mediaPlayerView.getVoicemailUri().equals(viewHolderVoicemailUri));
+ mediaPlayerView.setToPausedState(uri, mediaPlayer);
}
- @VisibleForTesting(otherwise = VisibleForTesting.NONE)
boolean isViewHolderExpanded() {
return isViewHolderExpanded;
}
@@ -117,25 +319,37 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On
return viewHolderId;
}
+ public Uri getViewHolderVoicemailUri() {
+ return viewHolderVoicemailUri;
+ }
+
interface NewVoicemailViewHolderListener {
- void onViewHolderExpanded(NewVoicemailViewHolder expandedViewHolder);
+ void expandViewHolderFirstTimeAndCollapseAllOtherVisibleViewHolders(
+ NewVoicemailViewHolder expandedViewHolder,
+ VoicemailEntry voicemailEntryOfViewHolder,
+ NewVoicemailViewHolderListener listener);
+
+ void collapseExpandedViewHolder(NewVoicemailViewHolder expandedViewHolder);
- void onViewHolderCollapsed(NewVoicemailViewHolder expandedViewHolder);
+ void pauseViewHolder(NewVoicemailViewHolder expandedViewHolder);
+
+ void resumePausedViewHolder(NewVoicemailViewHolder expandedViewHolder);
}
@Override
public void onClick(View v) {
LogUtil.i(
"NewVoicemailViewHolder.onClick",
- "voicemail id: %d, isViewHolderExpanded:%b",
+ "voicemail id: %d, isViewHolderCurrentlyExpanded:%b",
viewHolderId,
isViewHolderExpanded);
if (isViewHolderExpanded) {
- collapseViewHolder();
- voicemailViewHolderListener.onViewHolderCollapsed(this);
+ voicemailViewHolderListener.collapseExpandedViewHolder(this);
} else {
- expandViewHolder();
- voicemailViewHolderListener.onViewHolderExpanded(this);
+ voicemailViewHolderListener.expandViewHolderFirstTimeAndCollapseAllOtherVisibleViewHolders(
+ this,
+ Assert.isNotNull(voicemailEntryOfViewHolder),
+ Assert.isNotNull(voicemailViewHolderListener));
}
}
}
diff --git a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml
index 07ce86a1d..32726a9e5 100644
--- a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml
+++ b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml
@@ -71,6 +71,12 @@
android:orientation="horizontal"
android:weightSum="4">
+ <ImageButton
+ android:id="@+id/pauseButton"
+ style="@style/voicemail_media_player_buttons"
+ android:layout_weight="1"
+ android:src="@drawable/quantum_ic_pause_vd_theme_24"
+ android:visibility="gone"/>
<ImageButton
android:id="@+id/playButton"