/* * 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.app.FragmentManager; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.net.Uri; import android.provider.VoicemailContract; import android.provider.VoicemailContract.Voicemails; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.util.Pair; import android.text.TextUtils; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; import com.android.dialer.callintent.CallInitiationType.Type; import com.android.dialer.callintent.CallIntentBuilder; 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.common.concurrent.DialerExecutor.SuccessListener; import com.android.dialer.common.concurrent.DialerExecutor.Worker; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.precall.PreCall; import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.voicemail.listui.NewVoicemailViewHolder.NewVoicemailViewHolderListener; import com.android.dialer.voicemail.model.VoicemailEntry; import java.util.Locale; /** * The view of the media player that is visible when a {@link NewVoicemailViewHolder} is expanded. */ public final class NewVoicemailMediaPlayerView extends LinearLayout { private ImageButton playButton; private ImageButton pauseButton; private ImageButton speakerButton; private ImageButton phoneButton; private ImageButton deleteButton; private TextView currentSeekBarPosition; private SeekBar seekBarView; private Drawable voicemailSeekHandleDisabled; private TextView totalDurationView; private TextView voicemailLoadingStatusView; private Uri voicemailUri; private String numberVoicemailFrom; private String phoneAccountId; private String phoneAccountComponentName; private FragmentManager fragmentManager; private NewVoicemailViewHolder newVoicemailViewHolder; private NewVoicemailMediaPlayer mediaPlayer; private NewVoicemailViewHolderListener newVoicemailViewHolderListener; public NewVoicemailMediaPlayerView(Context context, AttributeSet attrs) { super(context, attrs); LogUtil.enterBlock("NewVoicemailMediaPlayer"); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.new_voicemail_media_player_layout, this); } @Override protected void onFinishInflate() { super.onFinishInflate(); LogUtil.enterBlock("NewVoicemailMediaPlayer.onFinishInflate"); initializeMediaPlayerButtonsAndViews(); setupListenersForMediaPlayerButtons(); } private void initializeMediaPlayerButtonsAndViews() { playButton = findViewById(R.id.playButton); pauseButton = findViewById(R.id.pauseButton); currentSeekBarPosition = findViewById(R.id.playback_position_text); seekBarView = findViewById(R.id.playback_seek); speakerButton = findViewById(R.id.speakerButton); 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); voicemailSeekHandleDisabled = getContext() .getResources() .getDrawable(R.drawable.ic_voicemail_seek_handle_disabled, getContext().getTheme()); } private void setupListenersForMediaPlayerButtons() { playButton.setOnClickListener(playButtonListener); pauseButton.setOnClickListener(pauseButtonListener); seekBarView.setOnSeekBarChangeListener(seekbarChangeListener); speakerButton.setOnClickListener(speakerButtonListener); phoneButton.setOnClickListener(phoneButtonListener); deleteButton.setOnClickListener(deleteButtonListener); } public void reset() { LogUtil.i( "NewVoicemailMediaPlayer.reset", "the uri for this is " + voicemailUri + " and number is " + numberVoicemailFrom); voicemailUri = null; voicemailLoadingStatusView.setVisibility(GONE); numberVoicemailFrom = null; phoneAccountId = null; phoneAccountComponentName = null; } /** * Can be called either when binding happens on the {@link NewVoicemailViewHolder} from {@link * NewVoicemailAdapter} or when a user expands a {@link NewVoicemailViewHolder}. During the * binding, since {@link NewVoicemailMediaPlayerView} is part of {@link NewVoicemailViewHolder}, * we have to ensure that during the binding the values from the {@link NewVoicemailAdapter} are * also propogated down to the {@link NewVoicemailMediaPlayerView} via {@link * NewVoicemailViewHolder}. In the case of when the {@link NewVoicemailViewHolder} is expanded, * the most recent value and states from the {@link NewVoicemailAdapter} are set for the expanded * {@link NewVoicemailMediaPlayerView}. * * @param viewHolder * @param voicemailEntryFromAdapter are the voicemail related values from the {@link * AnnotatedCallLog} converted into {@link VoicemailEntry} format. * @param fragmentManager * @param mp the media player passed down from the adapter * @param listener */ void bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView( NewVoicemailViewHolder viewHolder, @NonNull VoicemailEntry voicemailEntryFromAdapter, @NonNull FragmentManager fragmentManager, NewVoicemailMediaPlayer mp, NewVoicemailViewHolderListener listener) { Assert.isNotNull(voicemailEntryFromAdapter); Uri uri = Uri.parse(voicemailEntryFromAdapter.getVoicemailUri()); numberVoicemailFrom = voicemailEntryFromAdapter.getNumber().getNormalizedNumber(); phoneAccountId = voicemailEntryFromAdapter.getPhoneAccountId(); phoneAccountComponentName = voicemailEntryFromAdapter.getPhoneAccountComponentName(); Assert.isNotNull(viewHolder); Assert.isNotNull(uri); Assert.isNotNull(listener); Assert.isNotNull(totalDurationView); Assert.checkArgument(uri.equals(viewHolder.getViewHolderVoicemailUri())); LogUtil.i( "NewVoicemailMediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView", "Updating the viewholder:%d mediaPlayerView with uri value:%s", viewHolder.getViewHolderId(), uri.toString()); this.fragmentManager = fragmentManager; newVoicemailViewHolder = viewHolder; newVoicemailViewHolderListener = listener; mediaPlayer = mp; voicemailUri = uri; totalDurationView.setText( VoicemailEntryText.getVoicemailDuration(getContext(), voicemailEntryFromAdapter)); // Not sure if these are needed, but it'll ensure that onInflate() has atleast happened. initializeMediaPlayerButtonsAndViews(); setupListenersForMediaPlayerButtons(); // TODO(uabdullah): Handle seekbar seeking properly (a bug) seekBarView.setEnabled(false); seekBarView.setThumb(voicemailSeekHandleDisabled); updatePhoneIcon(numberVoicemailFrom); // During the binding we only send a request to the adapter to tell us what the // state of the media player should be and call that function. // This could be the paused state, or the playing state of the resume state. // Our job here is only to send the request upto the adapter and have it decide what we should // do. LogUtil.i( "NewVoicemailMediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView", "Updating media player values for id:" + viewHolder.getViewHolderId()); // During the binding make sure that the first time we just set the mediaplayer view // This does not take care of the constant update if (mp.isPlaying() && mp.getLastPlayedOrPlayingVoicemailUri().equals(voicemailUri)) { Assert.checkArgument( mp.getLastPlayedOrPlayingVoicemailUri() .equals(mp.getLastPreparedOrPreparingToPlayVoicemailUri())); LogUtil.i( "NewVoicemailMediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView", "show playing state"); playButton.setVisibility(GONE); pauseButton.setVisibility(VISIBLE); currentSeekBarPosition.setText(formatAsMinutesAndSeconds(mp.getCurrentPosition())); if (seekBarView.getMax() != mp.getDuration()) { seekBarView.setMax(mp.getDuration()); } seekBarView.setProgress(mp.getCurrentPosition()); } else if (mediaPlayer.isPaused() && mp.getLastPausedVoicemailUri().equals(voicemailUri)) { LogUtil.i( "NewVoicemailMediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView", "show paused state"); Assert.checkArgument(viewHolder.getViewHolderVoicemailUri().equals(voicemailUri)); playButton.setVisibility(VISIBLE); pauseButton.setVisibility(GONE); currentSeekBarPosition.setText(formatAsMinutesAndSeconds(mp.getCurrentPosition())); if (seekBarView.getMax() != mp.getDuration()) { seekBarView.setMax(mp.getDuration()); } seekBarView.setProgress(mp.getCurrentPosition()); } else { LogUtil.i( "NewVoicemailMediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView", "show reset state"); playButton.setVisibility(VISIBLE); pauseButton.setVisibility(GONE); seekBarView.setProgress(0); seekBarView.setMax(100); currentSeekBarPosition.setText(formatAsMinutesAndSeconds(0)); } } /** * Updates the phone icon depending if we can dial it or not. * *

Note: This must be called after the onClickListeners have been set, otherwise isClickable() * state is not maintained. */ private void updatePhoneIcon(@Nullable String numberVoicemailFrom) { // TODO(uabdullah): Handle restricted/blocked numbers (a bug) if (TextUtils.isEmpty(numberVoicemailFrom)) { phoneButton.setEnabled(false); phoneButton.setClickable(false); } else { phoneButton.setEnabled(true); phoneButton.setClickable(true); } } private final OnSeekBarChangeListener seekbarChangeListener = new OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBarfromProgress, int progress, boolean fromUser) { // TODO(uabdullah): Only for debugging purposes, to be removed. if (progress < 100) { LogUtil.i( "NewVoicemailMediaPlayer.seekbarChangeListener", "onProgressChanged, progress:%d, seekbarMax: %d, fromUser:%b", progress, seekBarfromProgress.getMax(), fromUser); } if (fromUser) { mediaPlayer.seekTo(progress); currentSeekBarPosition.setText(formatAsMinutesAndSeconds(progress)); } } @Override // TODO(uabdullah): Handle this case public void onStartTrackingTouch(SeekBar seekBar) { LogUtil.i("NewVoicemailMediaPlayer.onStartTrackingTouch", "does nothing for now"); } @Override // TODO(uabdullah): Handle this case public void onStopTrackingTouch(SeekBar seekBar) { LogUtil.i("NewVoicemailMediaPlayer.onStopTrackingTouch", "does nothing for now"); } }; private final View.OnClickListener pauseButtonListener = new View.OnClickListener() { @Override public void onClick(View view) { LogUtil.i( "NewVoicemailMediaPlayer.pauseButtonListener", "pauseMediaPlayerAndSetPausedStateOfViewHolder button for voicemailUri: %s", voicemailUri.toString()); Assert.checkArgument(playButton.getVisibility() == GONE); Assert.checkArgument(mediaPlayer != null); Assert.checkArgument( mediaPlayer.getLastPlayedOrPlayingVoicemailUri().equals((voicemailUri)), "the voicemail being played is the only voicemail that should" + " be paused. last played voicemail:%s, uri:%s", mediaPlayer.getLastPlayedOrPlayingVoicemailUri().toString(), voicemailUri.toString()); Assert.checkArgument( newVoicemailViewHolder.getViewHolderVoicemailUri().equals(voicemailUri), "viewholder uri and mediaplayer view should be the same."); newVoicemailViewHolderListener.pauseViewHolder(newVoicemailViewHolder); } }; /** * 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 public void onClick(View view) { LogUtil.i( "NewVoicemailMediaPlayer.playButtonListener", "play button for voicemailUri: %s", String.valueOf(voicemailUri)); if (mediaPlayer.getLastPausedVoicemailUri() != null && mediaPlayer .getLastPausedVoicemailUri() .toString() .contentEquals(voicemailUri.toString())) { LogUtil.i( "NewVoicemailMediaPlayer.playButtonListener", "resume playing voicemailUri: %s", voicemailUri.toString()); newVoicemailViewHolderListener.resumePausedViewHolder(newVoicemailViewHolder); } else { playVoicemailWhenAvailableLocally(); } } }; /** * Plays the voicemail when we are able to play the voicemail locally from the device. This * involves checking if the voicemail is available to play locally, if it is, then we setup the * Media Player to play the voicemail. If the voicemail is not available, then we need download * the voicemail from the voicemail server to the device, and then have the Media player play it. */ private void playVoicemailWhenAvailableLocally() { LogUtil.enterBlock("playVoicemailWhenAvailableLocally"); Worker, Pair> checkVoicemailHasContent = this::queryVoicemailHasContent; SuccessListener> checkVoicemailHasContentCallBack = this::prepareMediaPlayer; DialerExecutorComponent.get(getContext()) .dialerExecutorFactory() .createUiTaskBuilder(fragmentManager, "lookup_voicemail_content", checkVoicemailHasContent) .onSuccess(checkVoicemailHasContentCallBack) .build() .executeSerial(new Pair<>(getContext(), voicemailUri)); } private Pair queryVoicemailHasContent(Pair contextUriPair) { Context context = contextUriPair.first; Uri uri = contextUriPair.second; try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { return new Pair<>( cursor.getInt(cursor.getColumnIndex(VoicemailContract.Voicemails.HAS_CONTENT)) == 1, uri); } return new Pair<>(false, uri); } } /** * If the voicemail is available to play locally, setup the media player to play it. Otherwise * send a request to download the voicemail and then play it. */ private void prepareMediaPlayer(Pair booleanUriPair) { boolean voicemailAvailableLocally = booleanUriPair.first; Uri uri = booleanUriPair.second; LogUtil.i( "NewVoicemailMediaPlayer.prepareMediaPlayer", "voicemail available locally: %b for voicemailUri: %s", voicemailAvailableLocally, uri.toString()); if (voicemailAvailableLocally) { try { Assert.checkArgument(mediaPlayer != null, "media player should not have been null"); mediaPlayer.prepareMediaPlayerAndPlayVoicemailWhenReady(getContext(), uri); } catch (Exception e) { LogUtil.e( "NewVoicemailMediaPlayer.prepareMediaPlayer", "Exception when mediaPlayer.prepareMediaPlayerAndPlayVoicemailWhenReady" + "(getContext(), uri)\n" + e + "\n uri:" + uri + "context should not be null, its value is :" + getContext()); } } else { 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()); 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 = new View.OnClickListener() { @Override public void onClick(View view) { LogUtil.i( "NewVoicemailMediaPlayer.speakerButtonListener", "speaker request for voicemailUri: %s", voicemailUri.toString()); AudioManager audioManager = (AudioManager) getContext().getSystemService(AudioManager.class); audioManager.setMode(AudioManager.STREAM_MUSIC); if (audioManager.isSpeakerphoneOn()) { LogUtil.i( "NewVoicemailMediaPlayer.speakerButtonListener", "speaker was on, turning it off"); audioManager.setSpeakerphoneOn(false); } else { LogUtil.i( "NewVoicemailMediaPlayer.speakerButtonListener", "speaker was off, turning it on"); audioManager.setSpeakerphoneOn(true); } // TODO(uabdullah): Handle colors of speaker icon when speaker is on and off. } }; private final View.OnClickListener phoneButtonListener = new View.OnClickListener() { @Override public void onClick(View view) { LogUtil.i( "NewVoicemailMediaPlayer.phoneButtonListener", "phone request for voicemailUri: %s with number:%s", voicemailUri.toString(), numberVoicemailFrom); Assert.checkArgument( !TextUtils.isEmpty(numberVoicemailFrom), "number cannot be empty:" + numberVoicemailFrom); PreCall.start( getContext(), new CallIntentBuilder(numberVoicemailFrom, Type.VOICEMAIL_LOG) .setPhoneAccountHandle( TelecomUtil.composePhoneAccountHandle( phoneAccountComponentName, phoneAccountId))); } }; private final View.OnClickListener deleteButtonListener = new View.OnClickListener() { @Override public void onClick(View view) { LogUtil.i( "NewVoicemailMediaPlayer.deleteButtonListener", "delete voicemailUri %s", String.valueOf(voicemailUri)); newVoicemailViewHolderListener.deleteViewHolder( getContext(), fragmentManager, newVoicemailViewHolder, voicemailUri); } }; /** * This is only called to update the media player view of the seekbar, and the duration and the * play button. For constant updates the adapter should seek track. This is the state when a * voicemail is playing. */ public void updateSeekBarDurationAndShowPlayButton(NewVoicemailMediaPlayer mp) { if (!mp.isPlaying()) { return; } playButton.setVisibility(GONE); pauseButton.setVisibility(VISIBLE); voicemailLoadingStatusView.setVisibility(GONE); Assert.checkArgument( mp.equals(mediaPlayer), "there should only be one instance of a media player"); Assert.checkArgument( mediaPlayer.getLastPreparedOrPreparingToPlayVoicemailUri().equals(voicemailUri)); Assert.checkArgument(mediaPlayer.getLastPlayedOrPlayingVoicemailUri().equals(voicemailUri)); Assert.isNotNull(mediaPlayer, "media player should have been set on bind"); Assert.checkArgument(mediaPlayer.isPlaying()); Assert.checkArgument(mediaPlayer.getCurrentPosition() >= 0); Assert.checkArgument(mediaPlayer.getDuration() >= 0); Assert.checkArgument(playButton.getVisibility() == GONE); Assert.checkArgument(pauseButton.getVisibility() == VISIBLE); Assert.checkArgument(seekBarView.getVisibility() == VISIBLE); Assert.checkArgument(currentSeekBarPosition.getVisibility() == VISIBLE); currentSeekBarPosition.setText(formatAsMinutesAndSeconds(mediaPlayer.getCurrentPosition())); if (seekBarView.getMax() != mediaPlayer.getDuration()) { seekBarView.setMax(mediaPlayer.getDuration()); } seekBarView.setProgress(mediaPlayer.getCurrentPosition()); } /** * What the default state of an expanded media player view should look like. * * @param currentlyExpandedViewHolderOnScreen * @param mediaPlayer */ public void setToResetState( NewVoicemailViewHolder currentlyExpandedViewHolderOnScreen, NewVoicemailMediaPlayer mediaPlayer) { LogUtil.i( "NewVoicemailMediaPlayer.setToResetState", "update the seekbar for viewholder id:%d, mediaplayer view uri:%s, play button " + "visible:%b, pause button visible:%b", currentlyExpandedViewHolderOnScreen.getViewHolderId(), String.valueOf(voicemailUri), playButton.getVisibility() == VISIBLE, pauseButton.getVisibility() == VISIBLE); if (playButton.getVisibility() == GONE) { playButton.setVisibility(VISIBLE); pauseButton.setVisibility(GONE); } Assert.checkArgument(playButton.getVisibility() == VISIBLE); Assert.checkArgument(pauseButton.getVisibility() == GONE); Assert.checkArgument( !mediaPlayer.isPlaying(), "when resetting an expanded " + "state, there should be no voicemail playing"); Assert.checkArgument( mediaPlayer.getLastPlayedOrPlayingVoicemailUri().equals(Uri.EMPTY), "reset should have been called before updating its media player view"); currentSeekBarPosition.setText(formatAsMinutesAndSeconds(0)); seekBarView.setProgress(0); seekBarView.setMax(100); } public void setToPausedState(Uri toPausedState, NewVoicemailMediaPlayer mp) { LogUtil.i( "NewVoicemailMediaPlayer.setToPausedState", "toPausedState uri:%s, play button visible:%b, pause button visible:%b", toPausedState == null ? "null" : voicemailUri.toString(), playButton.getVisibility() == VISIBLE, pauseButton.getVisibility() == VISIBLE); playButton.setVisibility(VISIBLE); pauseButton.setVisibility(GONE); currentSeekBarPosition.setText(formatAsMinutesAndSeconds(mediaPlayer.getCurrentPosition())); if (seekBarView.getMax() != mediaPlayer.getDuration()) { seekBarView.setMax(mediaPlayer.getDuration()); } seekBarView.setProgress(mediaPlayer.getCurrentPosition()); Assert.checkArgument(voicemailUri.equals(toPausedState)); Assert.checkArgument(!mp.isPlaying()); Assert.checkArgument( mp.equals(mediaPlayer), "there should only be one instance of a media player"); Assert.checkArgument( this.mediaPlayer.getLastPreparedOrPreparingToPlayVoicemailUri().equals(voicemailUri)); Assert.checkArgument( this.mediaPlayer.getLastPlayedOrPlayingVoicemailUri().equals(voicemailUri)); Assert.checkArgument(this.mediaPlayer.getLastPausedVoicemailUri().equals(voicemailUri)); Assert.isNotNull(this.mediaPlayer, "media player should have been set on bind"); Assert.checkArgument(this.mediaPlayer.getCurrentPosition() >= 0); Assert.checkArgument(this.mediaPlayer.getDuration() >= 0); Assert.checkArgument(playButton.getVisibility() == VISIBLE); Assert.checkArgument(pauseButton.getVisibility() == GONE); Assert.checkArgument(seekBarView.getVisibility() == VISIBLE); Assert.checkArgument(currentSeekBarPosition.getVisibility() == VISIBLE); } @NonNull public Uri getVoicemailUri() { return voicemailUri; } private String formatAsMinutesAndSeconds(int millis) { int seconds = millis / 1000; int minutes = seconds / 60; seconds -= minutes * 60; if (minutes > 99) { minutes = 99; } return String.format(Locale.US, "%02d:%02d", minutes, seconds); } }