blob: 77dd9cc4b2438648903e9434c39743b7525c73bd [file] [log] [blame]
/*
* 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.database.Cursor;
import android.net.Uri;
import android.provider.VoicemailContract;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.support.v4.util.Pair;
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.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.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 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 TextView totalDurationView;
private Uri voicemailUri;
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);
}
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);
voicemailUri = 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.voicemailUri());
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();
// 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));
}
}
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);
}
};
private final View.OnClickListener playButtonListener =
new View.OnClickListener() {
@Override
public void onClick(View view) {
LogUtil.i(
"NewVoicemailMediaPlayer.playButtonListener",
"play button for voicemailUri: %s",
voicemailUri.toString());
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<Context, Uri>, Pair<Boolean, Uri>> checkVoicemailHasContent =
this::queryVoicemailHasContent;
SuccessListener<Pair<Boolean, Uri>> checkVoicemailHasContentCallBack = this::prepareMediaPlayer;
DialerExecutorComponent.get(getContext())
.dialerExecutorFactory()
.createUiTaskBuilder(fragmentManager, "lookup_voicemail_content", checkVoicemailHasContent)
.onSuccess(checkVoicemailHasContentCallBack)
.build()
.executeSerial(new Pair<>(getContext(), voicemailUri));
}
private Pair<Boolean, Uri> queryVoicemailHasContent(Pair<Context, Uri> 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<Boolean, Uri> 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 {
// TODO(a bug): Add logic for downloading voicemail content from the server.
LogUtil.i(
"NewVoicemailMediaPlayer.prepareVoicemailForMediaPlayer", "need to download content");
}
}
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());
}
};
private final View.OnClickListener phoneButtonListener =
new View.OnClickListener() {
@Override
public void onClick(View view) {
LogUtil.i(
"NewVoicemailMediaPlayer.phoneButtonListener",
"speaker request for voicemailUri: %s",
voicemailUri.toString());
}
};
private final View.OnClickListener deleteButtonListener =
new View.OnClickListener() {
@Override
public void onClick(View view) {
LogUtil.i(
"NewVoicemailMediaPlayer.deleteButtonListener",
"delete voicemailUri %s",
voicemailUri.toString());
}
};
/**
* 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);
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);
}
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
void setFragmentManager(FragmentManager fragmentManager) {
this.fragmentManager = fragmentManager;
}
}