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

Commit 4b43c451 authored by uabdullah's avatar uabdullah Committed by Copybara-Service
Browse files

Download and play voicemails from server when not locally available.

Test: Unit tests
PiperOrigin-RevId: 178791213
Change-Id: I9e68c561285988cc1def894f5c7ecf9715ecf6b6
parent 75812adc
Loading
Loading
Loading
Loading
+37 −2
Original line number Diff line number Diff line
@@ -56,7 +56,7 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<ViewHolder>
    int VOICEMAIL_ENTRY = 2;
  }

  private final Cursor cursor;
  private Cursor cursor;
  private final Clock clock;

  /** {@link Integer#MAX_VALUE} when the "Today" header should not be displayed. */
@@ -129,6 +129,11 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<ViewHolder>
    mediaPlayer.setOnErrorListener(onErrorListener);
  }

  public void updateCursor(Cursor updatedCursor) {
    this.cursor = updatedCursor;
    notifyDataSetChanged();
  }

  @Override
  public ViewHolder onCreateViewHolder(ViewGroup viewGroup, @RowType int viewType) {
    LogUtil.enterBlock("NewVoicemailAdapter.onCreateViewHolder");
@@ -714,7 +719,7 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<ViewHolder>
      // returned when currentlyExpandedViewHolderId = -1 (viewholder was collapsed)
      LogUtil.i(
          "NewVoicemailAdapter.getCurrentlyExpandedViewHolder",
          "no view holder found in newVoicemailViewHolderArrayMap size:%d for %d",
          "no view holder found in hashmap size:%d for %d",
          newVoicemailViewHolderArrayMap.size(),
          currentlyExpandedViewHolderId);
      // TODO(uabdullah): a bug Remove logging, temporarily here for debugging.
@@ -749,4 +754,34 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<ViewHolder>
    }
    return RowType.VOICEMAIL_ENTRY;
  }

  /**
   * This will be called once the voicemail that was attempted to be played (and was not locally
   * available) was downloaded from the server. However it is possible that by the time the download
   * was completed, the view holder was collapsed. In that case we shouldn't play the voicemail.
   */
  public void checkAndPlayVoicemail() {
    LogUtil.i(
        "NewVoicemailAdapter.checkAndPlayVoicemail",
        "expandedViewHolder:%d, inViewHolderSet:%b, MPRequestToDownload:%s",
        currentlyExpandedViewHolderId,
        isCurrentlyExpandedViewHolderInViewHolderSet(),
        String.valueOf(mediaPlayer.getVoicemailRequestedToDownload()));

    NewVoicemailViewHolder currentlyExpandedViewHolder = getCurrentlyExpandedViewHolder();
    if (currentlyExpandedViewHolderId != -1
        && isCurrentlyExpandedViewHolderInViewHolderSet()
        && currentlyExpandedViewHolder != null
        // Used to differentiate underlying table changes from voicemail downloads and other changes
        // (e.g delete)
        && mediaPlayer.getVoicemailRequestedToDownload() != null
        && (mediaPlayer
            .getVoicemailRequestedToDownload()
            .equals(currentlyExpandedViewHolder.getViewHolderVoicemailUri()))) {
      currentlyExpandedViewHolder.clickPlayButtonOfViewHoldersMediaPlayerView(
          currentlyExpandedViewHolder);
    } else {
      LogUtil.i("NewVoicemailAdapter.checkAndPlayVoicemail", "not playing downloaded voicemail");
    }
  }
}
+19 −5
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ public final class NewVoicemailFragment extends Fragment implements LoaderCallba
  @Override
  public View onCreateView(
      LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    LogUtil.enterBlock("NewVoicemailFragment.onCreateView");
    View view = inflater.inflate(R.layout.new_voicemail_call_log_fragment, container, false);
    recyclerView = view.findViewById(R.id.new_voicemail_call_log_recycler_view);
    getLoaderManager().restartLoader(0, null, this);
@@ -52,11 +53,24 @@ public final class NewVoicemailFragment extends Fragment implements LoaderCallba

  @Override
  public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    LogUtil.i("NewVoicemailFragment.onCreateLoader", "cursor size is %d", data.getCount());
    LogUtil.i("NewVoicemailFragment.onLoadFinished", "cursor size is %d", data.getCount());
    if (recyclerView.getAdapter() == null) {
      recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
      recyclerView.setAdapter(
          new NewVoicemailAdapter(
              data, System::currentTimeMillis, getActivity().getFragmentManager()));
    } else {
      // This would only be called in cases such as when voicemail has been fetched from the server
      // or a changed occurred in the annotated table changed (e.g deletes). To check if the change
      // was due to a voicemail download,
      // NewVoicemailAdapter.mediaPlayer.getVoicemailRequestedToDownload() is called.
      LogUtil.i(
          "NewVoicemailFragment.onLoadFinished",
          "adapter: %s was not null, checking and playing the voicemail if conditions met",
          recyclerView.getAdapter());
      ((NewVoicemailAdapter) recyclerView.getAdapter()).updateCursor(data);
      ((NewVoicemailAdapter) recyclerView.getAdapter()).checkAndPlayVoicemail();
    }
  }

  @Override
+20 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import java.io.IOException;
@@ -38,6 +39,7 @@ public class NewVoicemailMediaPlayer {
  private OnPreparedListener newVoicemailMediaPlayerOnPreparedListener;
  private OnCompletionListener newVoicemailMediaPlayerOnCompletionListener;
  private Uri pausedUri;
  @Nullable private Uri voicemailRequestedToDownload;

  public NewVoicemailMediaPlayer(@NonNull MediaPlayer player) {
    mediaPlayer = Assert.isNotNull(player);
@@ -94,6 +96,7 @@ public class NewVoicemailMediaPlayer {
    mediaPlayer.start();
    voicemailLastPlayedOrPlayingUri = startPlayingVoicemailUri;
    pausedUri = null;
    voicemailRequestedToDownload = null;
  }

  public void reset() {
@@ -102,6 +105,7 @@ public class NewVoicemailMediaPlayer {
    voicemailLastPlayedOrPlayingUri = null;
    voicemailUriLastPreparedOrPreparingToPlay = null;
    pausedUri = null;
    voicemailRequestedToDownload = null;
  }

  public void pauseMediaPlayer(Uri voicemailUri) {
@@ -134,6 +138,11 @@ public class NewVoicemailMediaPlayer {
    newVoicemailMediaPlayerOnCompletionListener = onCompletionListener;
  }

  public void setVoicemailRequestedToDownload(@NonNull Uri uri) {
    Assert.isNotNull(uri, "cannot download a null voicemail");
    voicemailRequestedToDownload = uri;
  }

  /**
   * Note: In some cases it's possible mediaPlayer.isPlaying() can return true, but
   * mediaPlayer.getCurrentPosition() can be greater than mediaPlayer.getDuration(), after which
@@ -182,6 +191,17 @@ public class NewVoicemailMediaPlayer {
    return mediaPlayer.getDuration();
  }

  /**
   * A null v/s non-value is important for the {@link NewVoicemailAdapter} to differentiate between
   * a underlying table change due to a voicemail being downloaded or something else (e.g delete).
   *
   * @return if there was a Uri that was requested to be downloaded from the server, null otherwise.
   */
  @Nullable
  public Uri getVoicemailRequestedToDownload() {
    return voicemailRequestedToDownload;
  }

  public boolean isPaused() {
    return pausedUri != null;
  }
+110 −9
Original line number Diff line number Diff line
@@ -17,12 +17,15 @@
package com.android.dialer.voicemail.listui;

import android.app.FragmentManager;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.VoicemailContract;
import android.provider.VoicemailContract.Voicemails;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.Nullable;
import android.support.v4.util.Pair;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@@ -45,7 +48,7 @@ import java.util.Locale;
/**
 * The view of the media player that is visible when a {@link NewVoicemailViewHolder} is expanded.
 */
public class NewVoicemailMediaPlayerView extends LinearLayout {
public final class NewVoicemailMediaPlayerView extends LinearLayout {

  private ImageButton playButton;
  private ImageButton pauseButton;
@@ -55,6 +58,7 @@ public class NewVoicemailMediaPlayerView extends LinearLayout {
  private TextView currentSeekBarPosition;
  private SeekBar seekBarView;
  private TextView totalDurationView;
  private TextView voicemailLoadingStatusView;
  private Uri voicemailUri;
  private FragmentManager fragmentManager;
  private NewVoicemailViewHolder newVoicemailViewHolder;
@@ -86,6 +90,7 @@ public class NewVoicemailMediaPlayerView extends LinearLayout {
    phoneButton = findViewById(R.id.phoneButton);
    deleteButton = findViewById(R.id.deleteButton);
    totalDurationView = findViewById(R.id.playback_seek_total_duration);
    voicemailLoadingStatusView = findViewById(R.id.playback_state_text);
  }

  private void setupListenersForMediaPlayerButtons() {
@@ -100,6 +105,7 @@ public class NewVoicemailMediaPlayerView extends LinearLayout {
  public void reset() {
    LogUtil.i("NewVoicemailMediaPlayer.reset", "the uri for this is " + voicemailUri);
    voicemailUri = null;
    voicemailLoadingStatusView.setVisibility(GONE);
  }

  /**
@@ -261,6 +267,16 @@ public class NewVoicemailMediaPlayerView extends LinearLayout {
        }
      };

  /**
   * Attempts to imitate clicking the play button. This is useful for when we the user attempted to
   * play a voicemail, but the media player didn't start playing till the voicemail was downloaded
   * from the server. However once we have the voicemail downloaded, we want to start playing, so as
   * to make it seem like that this is a continuation of the users initial play button click.
   */
  public final void clickPlayButton() {
    playButtonListener.onClick(null);
  }

  private final View.OnClickListener playButtonListener =
      new View.OnClickListener() {
        @Override
@@ -268,7 +284,8 @@ public class NewVoicemailMediaPlayerView extends LinearLayout {
          LogUtil.i(
              "NewVoicemailMediaPlayer.playButtonListener",
              "play button for voicemailUri: %s",
              voicemailUri.toString());
              String.valueOf(voicemailUri));

          if (mediaPlayer.getLastPausedVoicemailUri() != null
              && mediaPlayer
                  .getLastPausedVoicemailUri()
@@ -350,10 +367,83 @@ public class NewVoicemailMediaPlayerView extends LinearLayout {
                + getContext());
      }
    } else {
      // TODO(a bug): Add logic for downloading voicemail content from the server.
      LogUtil.i(
          "NewVoicemailMediaPlayer.prepareVoicemailForMediaPlayer", "need to download content");
      // Important to set since it allows the adapter to differentiate when to start playing the
      // voicemail, after it's downloaded.
      mediaPlayer.setVoicemailRequestedToDownload(uri);
      voicemailLoadingStatusView.setVisibility(VISIBLE);
      sendIntentToDownloadVoicemail(uri);
    }
  }

  private void sendIntentToDownloadVoicemail(Uri uri) {
    LogUtil.i("NewVoicemailMediaPlayer.sendIntentToDownloadVoicemail", "uri:%s", uri.toString());
    // Send voicemail fetch request.
    Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, uri);

    Worker<Pair<Context, Uri>, Pair<String, Uri>> getVoicemailSourcePackage =
        this::queryVoicemailSourcePackage;
    SuccessListener<Pair<String, Uri>> checkVoicemailHasSourcePackageCallBack = this::sendIntent;

    DialerExecutorComponent.get(getContext())
        .dialerExecutorFactory()
        .createUiTaskBuilder(fragmentManager, "lookup_voicemail_pkg", getVoicemailSourcePackage)
        .onSuccess(checkVoicemailHasSourcePackageCallBack)
        .build()
        .executeSerial(new Pair<>(getContext(), voicemailUri));
  }

  private void sendIntent(Pair<String, Uri> booleanUriPair) {
    String sourcePackage = booleanUriPair.first;
    Uri uri = booleanUriPair.second;
    LogUtil.i(
        "NewVoicemailMediaPlayer.sendIntent",
        "srcPkg:%s, uri:%%s",
        sourcePackage,
        String.valueOf(uri));
    Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, uri);
    intent.setPackage(sourcePackage);
    voicemailLoadingStatusView.setVisibility(VISIBLE);
    getContext().sendBroadcast(intent);
  }

  @Nullable
  private Pair<String, Uri> queryVoicemailSourcePackage(Pair<Context, Uri> contextUriPair) {
    LogUtil.enterBlock("NewVoicemailMediaPlayer.queryVoicemailSourcePackage");
    Context context = contextUriPair.first;
    Uri uri = contextUriPair.second;
    String sourcePackage;
    try (Cursor cursor =
        context
            .getContentResolver()
            .query(uri, new String[] {Voicemails.SOURCE_PACKAGE}, null, null, null)) {

      if (!hasContent(cursor)) {
        LogUtil.e(
            "NewVoicemailMediaPlayer.queryVoicemailSourcePackage",
            "uri: %s does not return a SOURCE_PACKAGE",
            uri.toString());
        sourcePackage = null;
      } else {
        sourcePackage = cursor.getString(0);
        LogUtil.i(
            "NewVoicemailMediaPlayer.queryVoicemailSourcePackage",
            "uri: %s has a SOURCE_PACKAGE: %s",
            uri.toString(),
            sourcePackage);
      }
      LogUtil.i(
          "NewVoicemailMediaPlayer.queryVoicemailSourcePackage",
          "uri: %s has a SOURCE_PACKAGE: %s",
          uri.toString(),
          sourcePackage);
    }
    return new Pair<>(sourcePackage, uri);
  }

  private boolean hasContent(Cursor cursor) {
    return cursor != null && cursor.moveToFirst();
  }

  private final View.OnClickListener speakerButtonListener =
@@ -386,6 +476,21 @@ public class NewVoicemailMediaPlayerView extends LinearLayout {
              "NewVoicemailMediaPlayer.deleteButtonListener",
              "delete voicemailUri %s",
              voicemailUri.toString());
          // TODO(uabdullah): This will be removed in cl/177404259. It only sets the has_content to
          // 0, to allow the annotated call log to change, and refresh the fragment. This is used to
          // demo and test the downloading of voicemails from the server.
          ContentValues contentValues = new ContentValues();
          contentValues.put("has_content", 0);

          try {
            getContext().getContentResolver().update(voicemailUri, contentValues, "type = 4", null);
          } catch (Exception e) {
            LogUtil.i(
                "NewVoicemailMediaPlayer.deleteButtonListener",
                "update has content of voicemailUri %s caused an error: %s",
                voicemailUri.toString(),
                e.toString());
          }
        }
      };

@@ -401,6 +506,7 @@ public class NewVoicemailMediaPlayerView extends LinearLayout {

    playButton.setVisibility(GONE);
    pauseButton.setVisibility(VISIBLE);
    voicemailLoadingStatusView.setVisibility(GONE);

    Assert.checkArgument(
        mp.equals(mediaPlayer), "there should only be one instance of a media player");
@@ -510,9 +616,4 @@ public class NewVoicemailMediaPlayerView extends LinearLayout {
    }
    return String.format(Locale.US, "%02d:%02d", minutes, seconds);
  }

  @VisibleForTesting(otherwise = VisibleForTesting.NONE)
  void setFragmentManager(FragmentManager fragmentManager) {
    this.fragmentManager = fragmentManager;
  }
}
+19 −0
Original line number Diff line number Diff line
@@ -187,6 +187,8 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On
        String.valueOf(viewHolderVoicemailUri));
    transcriptionTextView.setMaxLines(1);
    isViewHolderExpanded = false;

    mediaPlayerView.reset();
    mediaPlayerView.setVisibility(GONE);
  }

@@ -333,6 +335,23 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On
    return viewHolderVoicemailUri;
  }

  public void clickPlayButtonOfViewHoldersMediaPlayerView(
      NewVoicemailViewHolder expandedViewHolder) {
    LogUtil.i(
        "NewVoicemailViewHolder.clickPlayButtonOfViewHoldersMediaPlayerView",
        "expandedViewHolderID:%d",
        expandedViewHolder.getViewHolderId());

    Assert.checkArgument(
        mediaPlayerView.getVoicemailUri().equals(expandedViewHolder.getViewHolderVoicemailUri()));
    Assert.checkArgument(
        expandedViewHolder.getViewHolderVoicemailUri().equals(getViewHolderVoicemailUri()));
    Assert.checkArgument(
        mediaPlayerView.getVisibility() == View.VISIBLE,
        "the media player must be visible for viewholder id:%d, before we attempt to play");
    mediaPlayerView.clickPlayButton();
  }

  interface NewVoicemailViewHolderListener {
    void expandViewHolderFirstTimeAndCollapseAllOtherVisibleViewHolders(
        NewVoicemailViewHolder expandedViewHolder,
Loading