Download and play voicemails from server when not locally available.

Test: Unit tests
PiperOrigin-RevId: 178791213
Change-Id: I9e68c561285988cc1def894f5c7ecf9715ecf6b6
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
index 955c7da..671a39a 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
@@ -56,7 +56,7 @@
     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 @@
     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 @@
       // 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 @@
     }
     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");
+    }
+  }
 }
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
index 9a89dbe..82e704d 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
@@ -38,6 +38,7 @@
   @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 @@
 
   @Override
   public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
-    LogUtil.i("NewVoicemailFragment.onCreateLoader", "cursor size is %d", data.getCount());
-    recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
-    recyclerView.setAdapter(
-        new NewVoicemailAdapter(
-            data, System::currentTimeMillis, getActivity().getFragmentManager()));
+    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
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java
index 2d59b24..48062a8 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java
@@ -23,6 +23,7 @@
 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 @@
   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 @@
     mediaPlayer.start();
     voicemailLastPlayedOrPlayingUri = startPlayingVoicemailUri;
     pausedUri = null;
+    voicemailRequestedToDownload = null;
   }
 
   public void reset() {
@@ -102,6 +105,7 @@
     voicemailLastPlayedOrPlayingUri = null;
     voicemailUriLastPreparedOrPreparingToPlay = null;
     pausedUri = null;
+    voicemailRequestedToDownload = null;
   }
 
   public void pauseMediaPlayer(Uri voicemailUri) {
@@ -134,6 +138,11 @@
     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 @@
     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;
   }
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
index 77dd9cc..3f2de7d 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
@@ -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 @@
 /**
  * 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 @@
   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 @@
     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 void reset() {
     LogUtil.i("NewVoicemailMediaPlayer.reset", "the uri for this is " + voicemailUri);
     voicemailUri = null;
+    voicemailLoadingStatusView.setVisibility(GONE);
   }
 
   /**
@@ -261,6 +267,16 @@
         }
       };
 
+  /**
+   * 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 @@
           LogUtil.i(
               "NewVoicemailMediaPlayer.playButtonListener",
               "play button for voicemailUri: %s",
-              voicemailUri.toString());
+              String.valueOf(voicemailUri));
+
           if (mediaPlayer.getLastPausedVoicemailUri() != null
               && mediaPlayer
                   .getLastPausedVoicemailUri()
@@ -350,12 +367,85 @@
                 + 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 =
       new View.OnClickListener() {
         @Override
@@ -386,6 +476,21 @@
               "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 @@
 
     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 @@
     }
     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 d5b17a1..0725465 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
@@ -187,6 +187,8 @@
         String.valueOf(viewHolderVoicemailUri));
     transcriptionTextView.setMaxLines(1);
     isViewHolderExpanded = false;
+
+    mediaPlayerView.reset();
     mediaPlayerView.setVisibility(GONE);
   }
 
@@ -333,6 +335,23 @@
     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,
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 32726a9..3efcea5 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
@@ -22,7 +22,6 @@
     android:paddingTop="@dimen/voicemail_media_player_padding_top"
     android:orientation="vertical">
 
-  <!-- TODO(uabdullah): Make visibility gone (once implement fetching from vm server) -->
   <TextView
       android:id="@+id/playback_state_text"
       android:layout_width="match_parent"