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