From 4b43c451a7215ef05622fb9bdb6cb7d84e70efec Mon Sep 17 00:00:00 2001 From: uabdullah Date: Tue, 12 Dec 2017 11:41:57 -0800 Subject: Download and play voicemails from server when not locally available. Test: Unit tests PiperOrigin-RevId: 178791213 Change-Id: I9e68c561285988cc1def894f5c7ecf9715ecf6b6 --- .../voicemail/listui/NewVoicemailAdapter.java | 39 ++++++- .../voicemail/listui/NewVoicemailFragment.java | 24 ++++- .../voicemail/listui/NewVoicemailMediaPlayer.java | 20 ++++ .../listui/NewVoicemailMediaPlayerView.java | 119 +++++++++++++++++++-- .../voicemail/listui/NewVoicemailViewHolder.java | 19 ++++ .../layout/new_voicemail_media_player_layout.xml | 1 - 6 files changed, 205 insertions(+), 17 deletions(-) diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java index 955c7daee..671a39a67 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java @@ -56,7 +56,7 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter 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 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 // 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 } 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 9a89dbe3e..82e704d39 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java @@ -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 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 2d59b241b..48062a87d 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.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; } diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java index 77dd9cc4b..3f2de7d00 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 @@ 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> getVoicemailSourcePackage = + this::queryVoicemailSourcePackage; + SuccessListener> 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 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 queryVoicemailSourcePackage(Pair 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; - } } diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java index d5b17a19d..072546552 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java @@ -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, 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 32726a9e5..3efcea543 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"> -