Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2b01113c authored by uabdullah's avatar uabdullah Committed by android-build-merger
Browse files

Merge "Play voicemails, update seekbar timer, allow seeking, and maintain state after recycling."

am: 64262064

Change-Id: Ia633b8c1097e408d0f6fcf467b96270248c5d3fd
parents aa6f6fba 64262064
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
<!--
  ~ 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
  -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0"
    android:tint="?attr/colorControlNormal">
    <path
        android:fillColor="@android:color/white"
        android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
</vector>
 No newline at end of file
+468 −31

File changed.

Preview size limit exceeded, changes collapsed.

+188 −0
Original line number Diff line number Diff line
/*
 * 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;
  }
}
+334 −61

File changed.

Preview size limit exceeded, changes collapsed.

+236 −22
Original line number Diff line number Diff line
@@ -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 onViewHolderCollapsed(NewVoicemailViewHolder expandedViewHolder);
    void collapseExpandedViewHolder(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));
    }
  }
}
Loading