1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.dialer.voicemail.listui;
18
19import android.app.FragmentManager;
20import android.content.Context;
21import android.content.Intent;
22import android.database.Cursor;
23import android.graphics.drawable.Drawable;
24import android.media.AudioManager;
25import android.net.Uri;
26import android.provider.VoicemailContract;
27import android.provider.VoicemailContract.Voicemails;
28import android.support.annotation.NonNull;
29import android.support.annotation.Nullable;
30import android.support.v4.util.Pair;
31import android.text.TextUtils;
32import android.util.AttributeSet;
33import android.view.LayoutInflater;
34import android.view.View;
35import android.widget.ImageButton;
36import android.widget.LinearLayout;
37import android.widget.SeekBar;
38import android.widget.SeekBar.OnSeekBarChangeListener;
39import android.widget.TextView;
40import com.android.dialer.callintent.CallInitiationType.Type;
41import com.android.dialer.callintent.CallIntentBuilder;
42import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
43import com.android.dialer.common.Assert;
44import com.android.dialer.common.LogUtil;
45import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener;
46import com.android.dialer.common.concurrent.DialerExecutor.Worker;
47import com.android.dialer.common.concurrent.DialerExecutorComponent;
48import com.android.dialer.precall.PreCall;
49import com.android.dialer.telecom.TelecomUtil;
50import com.android.dialer.voicemail.listui.NewVoicemailViewHolder.NewVoicemailViewHolderListener;
51import com.android.dialer.voicemail.model.VoicemailEntry;
52import java.util.Locale;
53
54/**
55 * The view of the media player that is visible when a {@link NewVoicemailViewHolder} is expanded.
56 */
57public final class NewVoicemailMediaPlayerView extends LinearLayout {
58
59  private ImageButton playButton;
60  private ImageButton pauseButton;
61  private ImageButton speakerButton;
62  private ImageButton phoneButton;
63  private ImageButton deleteButton;
64  private TextView currentSeekBarPosition;
65  private SeekBar seekBarView;
66  private Drawable voicemailSeekHandleDisabled;
67
68  private TextView totalDurationView;
69  private TextView voicemailLoadingStatusView;
70  private Uri voicemailUri;
71  private String numberVoicemailFrom;
72  private String phoneAccountId;
73  private String phoneAccountComponentName;
74  private FragmentManager fragmentManager;
75  private NewVoicemailViewHolder newVoicemailViewHolder;
76  private NewVoicemailMediaPlayer mediaPlayer;
77  private NewVoicemailViewHolderListener newVoicemailViewHolderListener;
78
79  public NewVoicemailMediaPlayerView(Context context, AttributeSet attrs) {
80    super(context, attrs);
81    LogUtil.enterBlock("NewVoicemailMediaPlayer");
82    LayoutInflater inflater =
83        (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
84    inflater.inflate(R.layout.new_voicemail_media_player_layout, this);
85  }
86
87  @Override
88  protected void onFinishInflate() {
89    super.onFinishInflate();
90    LogUtil.enterBlock("NewVoicemailMediaPlayer.onFinishInflate");
91    initializeMediaPlayerButtonsAndViews();
92    setupListenersForMediaPlayerButtons();
93  }
94
95  private void initializeMediaPlayerButtonsAndViews() {
96    playButton = findViewById(R.id.playButton);
97    pauseButton = findViewById(R.id.pauseButton);
98    currentSeekBarPosition = findViewById(R.id.playback_position_text);
99    seekBarView = findViewById(R.id.playback_seek);
100    speakerButton = findViewById(R.id.speakerButton);
101    phoneButton = findViewById(R.id.phoneButton);
102    deleteButton = findViewById(R.id.deleteButton);
103    totalDurationView = findViewById(R.id.playback_seek_total_duration);
104    voicemailLoadingStatusView = findViewById(R.id.playback_state_text);
105
106    voicemailSeekHandleDisabled =
107        getContext()
108            .getResources()
109            .getDrawable(R.drawable.ic_voicemail_seek_handle_disabled, getContext().getTheme());
110  }
111
112  private void setupListenersForMediaPlayerButtons() {
113    playButton.setOnClickListener(playButtonListener);
114    pauseButton.setOnClickListener(pauseButtonListener);
115    seekBarView.setOnSeekBarChangeListener(seekbarChangeListener);
116    speakerButton.setOnClickListener(speakerButtonListener);
117    phoneButton.setOnClickListener(phoneButtonListener);
118    deleteButton.setOnClickListener(deleteButtonListener);
119  }
120
121  public void reset() {
122    LogUtil.i(
123        "NewVoicemailMediaPlayer.reset",
124        "the uri for this is " + voicemailUri + " and number is " + numberVoicemailFrom);
125    voicemailUri = null;
126    voicemailLoadingStatusView.setVisibility(GONE);
127    numberVoicemailFrom = null;
128    phoneAccountId = null;
129    phoneAccountComponentName = null;
130  }
131
132  /**
133   * Can be called either when binding happens on the {@link NewVoicemailViewHolder} from {@link
134   * NewVoicemailAdapter} or when a user expands a {@link NewVoicemailViewHolder}. During the
135   * binding, since {@link NewVoicemailMediaPlayerView} is part of {@link NewVoicemailViewHolder},
136   * we have to ensure that during the binding the values from the {@link NewVoicemailAdapter} are
137   * also propogated down to the {@link NewVoicemailMediaPlayerView} via {@link
138   * NewVoicemailViewHolder}. In the case of when the {@link NewVoicemailViewHolder} is expanded,
139   * the most recent value and states from the {@link NewVoicemailAdapter} are set for the expanded
140   * {@link NewVoicemailMediaPlayerView}.
141   *
142   * @param viewHolder
143   * @param voicemailEntryFromAdapter are the voicemail related values from the {@link
144   *     AnnotatedCallLog} converted into {@link VoicemailEntry} format.
145   * @param fragmentManager
146   * @param mp the media player passed down from the adapter
147   * @param listener
148   */
149  void bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView(
150      NewVoicemailViewHolder viewHolder,
151      @NonNull VoicemailEntry voicemailEntryFromAdapter,
152      @NonNull FragmentManager fragmentManager,
153      NewVoicemailMediaPlayer mp,
154      NewVoicemailViewHolderListener listener) {
155
156    Assert.isNotNull(voicemailEntryFromAdapter);
157    Uri uri = Uri.parse(voicemailEntryFromAdapter.voicemailUri());
158
159    numberVoicemailFrom = voicemailEntryFromAdapter.number().getNormalizedNumber();
160    phoneAccountId = voicemailEntryFromAdapter.phoneAccountId();
161    phoneAccountComponentName = voicemailEntryFromAdapter.phoneAccountComponentName();
162
163    Assert.isNotNull(viewHolder);
164    Assert.isNotNull(uri);
165    Assert.isNotNull(listener);
166    Assert.isNotNull(totalDurationView);
167    Assert.checkArgument(uri.equals(viewHolder.getViewHolderVoicemailUri()));
168
169    LogUtil.i(
170        "NewVoicemailMediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView",
171        "Updating the viewholder:%d mediaPlayerView with uri value:%s",
172        viewHolder.getViewHolderId(),
173        uri.toString());
174
175    this.fragmentManager = fragmentManager;
176
177    newVoicemailViewHolder = viewHolder;
178    newVoicemailViewHolderListener = listener;
179    mediaPlayer = mp;
180    voicemailUri = uri;
181    totalDurationView.setText(
182        VoicemailEntryText.getVoicemailDuration(getContext(), voicemailEntryFromAdapter));
183    // Not sure if these are needed, but it'll ensure that onInflate() has atleast happened.
184    initializeMediaPlayerButtonsAndViews();
185    setupListenersForMediaPlayerButtons();
186
187    // TODO(uabdullah): Handle seekbar seeking properly (a bug)
188    seekBarView.setEnabled(false);
189    seekBarView.setThumb(voicemailSeekHandleDisabled);
190
191    updatePhoneIcon(numberVoicemailFrom);
192
193    // During the binding we only send a request to the adapter to tell us what the
194    // state of the media player should be and call that function.
195    // This could be the paused state, or the playing state of the resume state.
196    // Our job here is only to send the request upto the adapter and have it decide what we should
197    // do.
198    LogUtil.i(
199        "NewVoicemailMediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView",
200        "Updating media player values for id:" + viewHolder.getViewHolderId());
201
202    // During the binding make sure that the first time we just set the mediaplayer view
203    // This does not take care of the constant update
204    if (mp.isPlaying() && mp.getLastPlayedOrPlayingVoicemailUri().equals(voicemailUri)) {
205      Assert.checkArgument(
206          mp.getLastPlayedOrPlayingVoicemailUri()
207              .equals(mp.getLastPreparedOrPreparingToPlayVoicemailUri()));
208      LogUtil.i(
209          "NewVoicemailMediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView",
210          "show playing state");
211      playButton.setVisibility(GONE);
212      pauseButton.setVisibility(VISIBLE);
213      currentSeekBarPosition.setText(formatAsMinutesAndSeconds(mp.getCurrentPosition()));
214
215      if (seekBarView.getMax() != mp.getDuration()) {
216        seekBarView.setMax(mp.getDuration());
217      }
218      seekBarView.setProgress(mp.getCurrentPosition());
219
220    } else if (mediaPlayer.isPaused() && mp.getLastPausedVoicemailUri().equals(voicemailUri)) {
221      LogUtil.i(
222          "NewVoicemailMediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView",
223          "show paused state");
224      Assert.checkArgument(viewHolder.getViewHolderVoicemailUri().equals(voicemailUri));
225      playButton.setVisibility(VISIBLE);
226      pauseButton.setVisibility(GONE);
227      currentSeekBarPosition.setText(formatAsMinutesAndSeconds(mp.getCurrentPosition()));
228      if (seekBarView.getMax() != mp.getDuration()) {
229        seekBarView.setMax(mp.getDuration());
230      }
231      seekBarView.setProgress(mp.getCurrentPosition());
232
233    } else {
234      LogUtil.i(
235          "NewVoicemailMediaPlayerView.bindValuesFromAdapterOfExpandedViewHolderMediaPlayerView",
236          "show reset state");
237      playButton.setVisibility(VISIBLE);
238      pauseButton.setVisibility(GONE);
239      seekBarView.setProgress(0);
240      seekBarView.setMax(100);
241      currentSeekBarPosition.setText(formatAsMinutesAndSeconds(0));
242    }
243  }
244
245  /**
246   * Updates the phone icon depending if we can dial it or not.
247   *
248   * <p>Note: This must be called after the onClickListeners have been set, otherwise isClickable()
249   * state is not maintained.
250   */
251  private void updatePhoneIcon(@Nullable String numberVoicemailFrom) {
252    // TODO(uabdullah): Handle restricted/blocked numbers (a bug)
253    if (TextUtils.isEmpty(numberVoicemailFrom)) {
254      phoneButton.setEnabled(false);
255      phoneButton.setClickable(false);
256    } else {
257      phoneButton.setEnabled(true);
258      phoneButton.setClickable(true);
259    }
260  }
261
262  private final OnSeekBarChangeListener seekbarChangeListener =
263      new OnSeekBarChangeListener() {
264        @Override
265        public void onProgressChanged(SeekBar seekBarfromProgress, int progress, boolean fromUser) {
266          // TODO(uabdullah): Only for debugging purposes, to be removed.
267          if (progress < 100) {
268            LogUtil.i(
269                "NewVoicemailMediaPlayer.seekbarChangeListener",
270                "onProgressChanged, progress:%d, seekbarMax: %d, fromUser:%b",
271                progress,
272                seekBarfromProgress.getMax(),
273                fromUser);
274          }
275
276          if (fromUser) {
277            mediaPlayer.seekTo(progress);
278            currentSeekBarPosition.setText(formatAsMinutesAndSeconds(progress));
279          }
280        }
281
282        @Override
283        // TODO(uabdullah): Handle this case
284        public void onStartTrackingTouch(SeekBar seekBar) {
285          LogUtil.i("NewVoicemailMediaPlayer.onStartTrackingTouch", "does nothing for now");
286        }
287
288        @Override
289        // TODO(uabdullah): Handle this case
290        public void onStopTrackingTouch(SeekBar seekBar) {
291          LogUtil.i("NewVoicemailMediaPlayer.onStopTrackingTouch", "does nothing for now");
292        }
293      };
294
295  private final View.OnClickListener pauseButtonListener =
296      new View.OnClickListener() {
297        @Override
298        public void onClick(View view) {
299          LogUtil.i(
300              "NewVoicemailMediaPlayer.pauseButtonListener",
301              "pauseMediaPlayerAndSetPausedStateOfViewHolder button for voicemailUri: %s",
302              voicemailUri.toString());
303
304          Assert.checkArgument(playButton.getVisibility() == GONE);
305          Assert.checkArgument(mediaPlayer != null);
306          Assert.checkArgument(
307              mediaPlayer.getLastPlayedOrPlayingVoicemailUri().equals((voicemailUri)),
308              "the voicemail being played is the only voicemail that should"
309                  + " be paused. last played voicemail:%s, uri:%s",
310              mediaPlayer.getLastPlayedOrPlayingVoicemailUri().toString(),
311              voicemailUri.toString());
312          Assert.checkArgument(
313              newVoicemailViewHolder.getViewHolderVoicemailUri().equals(voicemailUri),
314              "viewholder uri and mediaplayer view should be the same.");
315          newVoicemailViewHolderListener.pauseViewHolder(newVoicemailViewHolder);
316        }
317      };
318
319  /**
320   * Attempts to imitate clicking the play button. This is useful for when we the user attempted to
321   * play a voicemail, but the media player didn't start playing till the voicemail was downloaded
322   * from the server. However once we have the voicemail downloaded, we want to start playing, so as
323   * to make it seem like that this is a continuation of the users initial play button click.
324   */
325  public final void clickPlayButton() {
326    playButtonListener.onClick(null);
327  }
328
329  private final View.OnClickListener playButtonListener =
330      new View.OnClickListener() {
331        @Override
332        public void onClick(View view) {
333          LogUtil.i(
334              "NewVoicemailMediaPlayer.playButtonListener",
335              "play button for voicemailUri: %s",
336              String.valueOf(voicemailUri));
337
338          if (mediaPlayer.getLastPausedVoicemailUri() != null
339              && mediaPlayer
340                  .getLastPausedVoicemailUri()
341                  .toString()
342                  .contentEquals(voicemailUri.toString())) {
343            LogUtil.i(
344                "NewVoicemailMediaPlayer.playButtonListener",
345                "resume playing voicemailUri: %s",
346                voicemailUri.toString());
347
348            newVoicemailViewHolderListener.resumePausedViewHolder(newVoicemailViewHolder);
349
350          } else {
351            playVoicemailWhenAvailableLocally();
352          }
353        }
354      };
355
356  /**
357   * Plays the voicemail when we are able to play the voicemail locally from the device. This
358   * involves checking if the voicemail is available to play locally, if it is, then we setup the
359   * Media Player to play the voicemail. If the voicemail is not available, then we need download
360   * the voicemail from the voicemail server to the device, and then have the Media player play it.
361   */
362  private void playVoicemailWhenAvailableLocally() {
363    LogUtil.enterBlock("playVoicemailWhenAvailableLocally");
364    Worker<Pair<Context, Uri>, Pair<Boolean, Uri>> checkVoicemailHasContent =
365        this::queryVoicemailHasContent;
366    SuccessListener<Pair<Boolean, Uri>> checkVoicemailHasContentCallBack = this::prepareMediaPlayer;
367
368    DialerExecutorComponent.get(getContext())
369        .dialerExecutorFactory()
370        .createUiTaskBuilder(fragmentManager, "lookup_voicemail_content", checkVoicemailHasContent)
371        .onSuccess(checkVoicemailHasContentCallBack)
372        .build()
373        .executeSerial(new Pair<>(getContext(), voicemailUri));
374  }
375
376  private Pair<Boolean, Uri> queryVoicemailHasContent(Pair<Context, Uri> contextUriPair) {
377    Context context = contextUriPair.first;
378    Uri uri = contextUriPair.second;
379
380    try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
381      if (cursor != null && cursor.moveToFirst()) {
382        return new Pair<>(
383            cursor.getInt(cursor.getColumnIndex(VoicemailContract.Voicemails.HAS_CONTENT)) == 1,
384            uri);
385      }
386      return new Pair<>(false, uri);
387    }
388  }
389
390  /**
391   * If the voicemail is available to play locally, setup the media player to play it. Otherwise
392   * send a request to download the voicemail and then play it.
393   */
394  private void prepareMediaPlayer(Pair<Boolean, Uri> booleanUriPair) {
395    boolean voicemailAvailableLocally = booleanUriPair.first;
396    Uri uri = booleanUriPair.second;
397    LogUtil.i(
398        "NewVoicemailMediaPlayer.prepareMediaPlayer",
399        "voicemail available locally: %b for voicemailUri: %s",
400        voicemailAvailableLocally,
401        uri.toString());
402
403    if (voicemailAvailableLocally) {
404      try {
405        Assert.checkArgument(mediaPlayer != null, "media player should not have been null");
406        mediaPlayer.prepareMediaPlayerAndPlayVoicemailWhenReady(getContext(), uri);
407      } catch (Exception e) {
408        LogUtil.e(
409            "NewVoicemailMediaPlayer.prepareMediaPlayer",
410            "Exception when mediaPlayer.prepareMediaPlayerAndPlayVoicemailWhenReady"
411                + "(getContext(), uri)\n"
412                + e
413                + "\n uri:"
414                + uri
415                + "context should not be null, its value is :"
416                + getContext());
417      }
418    } else {
419      LogUtil.i(
420          "NewVoicemailMediaPlayer.prepareVoicemailForMediaPlayer", "need to download content");
421      // Important to set since it allows the adapter to differentiate when to start playing the
422      // voicemail, after it's downloaded.
423      mediaPlayer.setVoicemailRequestedToDownload(uri);
424      voicemailLoadingStatusView.setVisibility(VISIBLE);
425      sendIntentToDownloadVoicemail(uri);
426    }
427  }
428
429  private void sendIntentToDownloadVoicemail(Uri uri) {
430    LogUtil.i("NewVoicemailMediaPlayer.sendIntentToDownloadVoicemail", "uri:%s", uri.toString());
431
432    Worker<Pair<Context, Uri>, Pair<String, Uri>> getVoicemailSourcePackage =
433        this::queryVoicemailSourcePackage;
434    SuccessListener<Pair<String, Uri>> checkVoicemailHasSourcePackageCallBack = this::sendIntent;
435
436    DialerExecutorComponent.get(getContext())
437        .dialerExecutorFactory()
438        .createUiTaskBuilder(fragmentManager, "lookup_voicemail_pkg", getVoicemailSourcePackage)
439        .onSuccess(checkVoicemailHasSourcePackageCallBack)
440        .build()
441        .executeSerial(new Pair<>(getContext(), voicemailUri));
442  }
443
444  private void sendIntent(Pair<String, Uri> booleanUriPair) {
445    String sourcePackage = booleanUriPair.first;
446    Uri uri = booleanUriPair.second;
447    LogUtil.i(
448        "NewVoicemailMediaPlayer.sendIntent",
449        "srcPkg:%s, uri:%s",
450        sourcePackage,
451        String.valueOf(uri));
452    Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, uri);
453    intent.setPackage(sourcePackage);
454    voicemailLoadingStatusView.setVisibility(VISIBLE);
455    getContext().sendBroadcast(intent);
456  }
457
458  @Nullable
459  private Pair<String, Uri> queryVoicemailSourcePackage(Pair<Context, Uri> contextUriPair) {
460    LogUtil.enterBlock("NewVoicemailMediaPlayer.queryVoicemailSourcePackage");
461    Context context = contextUriPair.first;
462    Uri uri = contextUriPair.second;
463    String sourcePackage;
464    try (Cursor cursor =
465        context
466            .getContentResolver()
467            .query(uri, new String[] {Voicemails.SOURCE_PACKAGE}, null, null, null)) {
468
469      if (!hasContent(cursor)) {
470        LogUtil.e(
471            "NewVoicemailMediaPlayer.queryVoicemailSourcePackage",
472            "uri: %s does not return a SOURCE_PACKAGE",
473            uri.toString());
474        sourcePackage = null;
475      } else {
476        sourcePackage = cursor.getString(0);
477        LogUtil.i(
478            "NewVoicemailMediaPlayer.queryVoicemailSourcePackage",
479            "uri: %s has a SOURCE_PACKAGE: %s",
480            uri.toString(),
481            sourcePackage);
482      }
483      LogUtil.i(
484          "NewVoicemailMediaPlayer.queryVoicemailSourcePackage",
485          "uri: %s has a SOURCE_PACKAGE: %s",
486          uri.toString(),
487          sourcePackage);
488    }
489    return new Pair<>(sourcePackage, uri);
490  }
491
492  private boolean hasContent(Cursor cursor) {
493    return cursor != null && cursor.moveToFirst();
494  }
495
496  private final View.OnClickListener speakerButtonListener =
497      new View.OnClickListener() {
498        @Override
499        public void onClick(View view) {
500          LogUtil.i(
501              "NewVoicemailMediaPlayer.speakerButtonListener",
502              "speaker request for voicemailUri: %s",
503              voicemailUri.toString());
504          AudioManager audioManager =
505              (AudioManager) getContext().getSystemService(AudioManager.class);
506          audioManager.setMode(AudioManager.STREAM_MUSIC);
507          if (audioManager.isSpeakerphoneOn()) {
508            LogUtil.i(
509                "NewVoicemailMediaPlayer.speakerButtonListener", "speaker was on, turning it off");
510            audioManager.setSpeakerphoneOn(false);
511          } else {
512            LogUtil.i(
513                "NewVoicemailMediaPlayer.speakerButtonListener", "speaker was off, turning it on");
514            audioManager.setSpeakerphoneOn(true);
515          }
516          // TODO(uabdullah): Handle colors of speaker icon when speaker is on and off.
517        }
518      };
519
520  private final View.OnClickListener phoneButtonListener =
521      new View.OnClickListener() {
522        @Override
523        public void onClick(View view) {
524          LogUtil.i(
525              "NewVoicemailMediaPlayer.phoneButtonListener",
526              "phone request for voicemailUri: %s with number:%s",
527              voicemailUri.toString(),
528              numberVoicemailFrom);
529
530          Assert.checkArgument(
531              !TextUtils.isEmpty(numberVoicemailFrom),
532              "number cannot be empty:" + numberVoicemailFrom);
533          PreCall.start(
534              getContext(),
535              new CallIntentBuilder(numberVoicemailFrom, Type.VOICEMAIL_LOG)
536                  .setPhoneAccountHandle(
537                      TelecomUtil.composePhoneAccountHandle(
538                          phoneAccountComponentName, phoneAccountId)));
539        }
540      };
541
542  private final View.OnClickListener deleteButtonListener =
543      new View.OnClickListener() {
544        @Override
545        public void onClick(View view) {
546          LogUtil.i(
547              "NewVoicemailMediaPlayer.deleteButtonListener",
548              "delete voicemailUri %s",
549              String.valueOf(voicemailUri));
550          newVoicemailViewHolderListener.deleteViewHolder(
551              getContext(), fragmentManager, newVoicemailViewHolder, voicemailUri);
552        }
553      };
554
555  /**
556   * This is only called to update the media player view of the seekbar, and the duration and the
557   * play button. For constant updates the adapter should seek track. This is the state when a
558   * voicemail is playing.
559   */
560  public void updateSeekBarDurationAndShowPlayButton(NewVoicemailMediaPlayer mp) {
561    if (!mp.isPlaying()) {
562      return;
563    }
564
565    playButton.setVisibility(GONE);
566    pauseButton.setVisibility(VISIBLE);
567    voicemailLoadingStatusView.setVisibility(GONE);
568
569    Assert.checkArgument(
570        mp.equals(mediaPlayer), "there should only be one instance of a media player");
571    Assert.checkArgument(
572        mediaPlayer.getLastPreparedOrPreparingToPlayVoicemailUri().equals(voicemailUri));
573    Assert.checkArgument(mediaPlayer.getLastPlayedOrPlayingVoicemailUri().equals(voicemailUri));
574    Assert.isNotNull(mediaPlayer, "media player should have been set on bind");
575    Assert.checkArgument(mediaPlayer.isPlaying());
576    Assert.checkArgument(mediaPlayer.getCurrentPosition() >= 0);
577    Assert.checkArgument(mediaPlayer.getDuration() >= 0);
578    Assert.checkArgument(playButton.getVisibility() == GONE);
579    Assert.checkArgument(pauseButton.getVisibility() == VISIBLE);
580    Assert.checkArgument(seekBarView.getVisibility() == VISIBLE);
581    Assert.checkArgument(currentSeekBarPosition.getVisibility() == VISIBLE);
582
583    currentSeekBarPosition.setText(formatAsMinutesAndSeconds(mediaPlayer.getCurrentPosition()));
584    if (seekBarView.getMax() != mediaPlayer.getDuration()) {
585      seekBarView.setMax(mediaPlayer.getDuration());
586    }
587    seekBarView.setProgress(mediaPlayer.getCurrentPosition());
588  }
589
590  /**
591   * What the default state of an expanded media player view should look like.
592   *
593   * @param currentlyExpandedViewHolderOnScreen
594   * @param mediaPlayer
595   */
596  public void setToResetState(
597      NewVoicemailViewHolder currentlyExpandedViewHolderOnScreen,
598      NewVoicemailMediaPlayer mediaPlayer) {
599    LogUtil.i(
600        "NewVoicemailMediaPlayer.setToResetState",
601        "update the seekbar for viewholder id:%d, mediaplayer view uri:%s, play button "
602            + "visible:%b, pause button visible:%b",
603        currentlyExpandedViewHolderOnScreen.getViewHolderId(),
604        String.valueOf(voicemailUri),
605        playButton.getVisibility() == VISIBLE,
606        pauseButton.getVisibility() == VISIBLE);
607
608    if (playButton.getVisibility() == GONE) {
609      playButton.setVisibility(VISIBLE);
610      pauseButton.setVisibility(GONE);
611    }
612
613    Assert.checkArgument(playButton.getVisibility() == VISIBLE);
614    Assert.checkArgument(pauseButton.getVisibility() == GONE);
615
616    Assert.checkArgument(
617        !mediaPlayer.isPlaying(),
618        "when resetting an expanded " + "state, there should be no voicemail playing");
619
620    Assert.checkArgument(
621        mediaPlayer.getLastPlayedOrPlayingVoicemailUri().equals(Uri.EMPTY),
622        "reset should have been called before updating its media player view");
623    currentSeekBarPosition.setText(formatAsMinutesAndSeconds(0));
624    seekBarView.setProgress(0);
625    seekBarView.setMax(100);
626  }
627
628  public void setToPausedState(Uri toPausedState, NewVoicemailMediaPlayer mp) {
629    LogUtil.i(
630        "NewVoicemailMediaPlayer.setToPausedState",
631        "toPausedState uri:%s, play button visible:%b, pause button visible:%b",
632        toPausedState == null ? "null" : voicemailUri.toString(),
633        playButton.getVisibility() == VISIBLE,
634        pauseButton.getVisibility() == VISIBLE);
635
636    playButton.setVisibility(VISIBLE);
637    pauseButton.setVisibility(GONE);
638
639    currentSeekBarPosition.setText(formatAsMinutesAndSeconds(mediaPlayer.getCurrentPosition()));
640    if (seekBarView.getMax() != mediaPlayer.getDuration()) {
641      seekBarView.setMax(mediaPlayer.getDuration());
642    }
643    seekBarView.setProgress(mediaPlayer.getCurrentPosition());
644
645    Assert.checkArgument(voicemailUri.equals(toPausedState));
646    Assert.checkArgument(!mp.isPlaying());
647    Assert.checkArgument(
648        mp.equals(mediaPlayer), "there should only be one instance of a media player");
649    Assert.checkArgument(
650        this.mediaPlayer.getLastPreparedOrPreparingToPlayVoicemailUri().equals(voicemailUri));
651    Assert.checkArgument(
652        this.mediaPlayer.getLastPlayedOrPlayingVoicemailUri().equals(voicemailUri));
653    Assert.checkArgument(this.mediaPlayer.getLastPausedVoicemailUri().equals(voicemailUri));
654    Assert.isNotNull(this.mediaPlayer, "media player should have been set on bind");
655    Assert.checkArgument(this.mediaPlayer.getCurrentPosition() >= 0);
656    Assert.checkArgument(this.mediaPlayer.getDuration() >= 0);
657    Assert.checkArgument(playButton.getVisibility() == VISIBLE);
658    Assert.checkArgument(pauseButton.getVisibility() == GONE);
659    Assert.checkArgument(seekBarView.getVisibility() == VISIBLE);
660    Assert.checkArgument(currentSeekBarPosition.getVisibility() == VISIBLE);
661  }
662
663  @NonNull
664  public Uri getVoicemailUri() {
665    return voicemailUri;
666  }
667
668  private String formatAsMinutesAndSeconds(int millis) {
669    int seconds = millis / 1000;
670    int minutes = seconds / 60;
671    seconds -= minutes * 60;
672    if (minutes > 99) {
673      minutes = 99;
674    }
675    return String.format(Locale.US, "%02d:%02d", minutes, seconds);
676  }
677}
678