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"