Access incoming audio files after user taps Play

For platforms with insecure handling of media files, media files should
be opened only after the user chooses to play the media.

Change-Id: I5f9bbd1f8468a704a5962f0dddd3c8b11bba8bea
This commit is contained in:
Shri Borde
2015-08-21 10:17:46 -07:00
parent d4b7315d55
commit eba0949bc6
6 changed files with 90 additions and 23 deletions
@@ -23,6 +23,7 @@ import android.provider.MediaStore.Video.Thumbnails;
import com.android.messaging.Factory; import com.android.messaging.Factory;
import com.android.messaging.util.MediaMetadataRetrieverWrapper; import com.android.messaging.util.MediaMetadataRetrieverWrapper;
import com.android.messaging.util.MediaUtil;
import com.android.messaging.util.OsUtil; import com.android.messaging.util.OsUtil;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@@ -41,7 +42,7 @@ public class VideoThumbnailRequest extends ImageRequest<UriImageRequestDescripto
} }
public static boolean shouldShowIncomingVideoThumbnails() { public static boolean shouldShowIncomingVideoThumbnails() {
return OsUtil.isAtLeastM(); return MediaUtil.canAutoAccessIncomingMedia();
} }
@Override @Override
@@ -269,7 +269,8 @@ public class AttachmentPreviewFactory {
final View view = layoutInflater.inflate(layoutId, parent, false /* attachToRoot */); final View view = layoutInflater.inflate(layoutId, parent, false /* attachToRoot */);
final AudioAttachmentView audioView = (AudioAttachmentView) final AudioAttachmentView audioView = (AudioAttachmentView)
view.findViewById(R.id.audio_attachment_view); view.findViewById(R.id.audio_attachment_view);
audioView.bindMessagePartData(attachmentData, false /* incoming */); audioView.bindMessagePartData(
attachmentData, false /* incoming */, false /* showAsSelected */);
return view; return view;
} }
@@ -41,6 +41,7 @@ import com.android.messaging.ui.mediapicker.PausableChronometer;
import com.android.messaging.util.Assert; import com.android.messaging.util.Assert;
import com.android.messaging.util.ContentType; import com.android.messaging.util.ContentType;
import com.android.messaging.util.LogUtil; import com.android.messaging.util.LogUtil;
import com.android.messaging.util.MediaUtil;
import com.android.messaging.util.UiUtils; import com.android.messaging.util.UiUtils;
/** /**
@@ -74,11 +75,15 @@ public class AudioAttachmentView extends LinearLayout {
private int mClipPathWidth; private int mClipPathWidth;
private int mClipPathHeight; private int mClipPathHeight;
// Indicates whether the attachment view is to be styled as a part of an incoming message. private boolean mUseIncomingStyle;
private boolean mShowAsIncoming; private int mThemeColor;
private boolean mStartPlayAfterPrepare;
// should the MediaPlayer be prepared lazily when the user chooses to play the audio (as
// opposed to preparing it early, on bind)
private boolean mPrepareOnPlayback;
private boolean mPrepared; private boolean mPrepared;
private boolean mPlaybackFinished; private boolean mPlaybackFinished; // Was the audio played all the way to the end
private final int mMode; private final int mMode;
public AudioAttachmentView(final Context context, final AttributeSet attrs) { public AudioAttachmentView(final Context context, final AttributeSet attrs) {
@@ -108,7 +113,7 @@ public class AudioAttachmentView extends LinearLayout {
mPlayPauseButton.setOnClickListener(new OnClickListener() { mPlayPauseButton.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(final View v) { public void onClick(final View v) {
setupMediaPlayer(); // Has the MediaPlayer already been prepared?
if (mMediaPlayer != null && mPrepared) { if (mMediaPlayer != null && mPrepared) {
if (mMediaPlayer.isPlaying()) { if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause(); mMediaPlayer.pause();
@@ -117,6 +122,17 @@ public class AudioAttachmentView extends LinearLayout {
} else { } else {
playAudio(); playAudio();
} }
} else {
// Either eager preparation is still going on (the user must have clicked
// the Play button immediately after the view is bound) or this is lazy
// preparation.
if (mStartPlayAfterPrepare) {
// The user is (starting and) pausing before the MediaPlayer is prepared
mStartPlayAfterPrepare = false;
} else {
mStartPlayAfterPrepare = true;
setupMediaPlayer();
}
} }
updatePlayPauseButtonState(); updatePlayPauseButtonState();
} }
@@ -125,26 +141,52 @@ public class AudioAttachmentView extends LinearLayout {
initializeViewsForMode(); initializeViewsForMode();
} }
private void updateChronometerVisibility(final boolean playing) {
if (mChronometer.getVisibility() == View.GONE) {
// The chronometer is always GONE for LAYOUT_MODE_SUB_COMPACT
Assert.equals(LAYOUT_MODE_SUB_COMPACT, mMode);
return;
}
if (mPrepareOnPlayback) {
// For lazy preparation, the chronometer will only be shown during playback
mChronometer.setVisibility(playing ? View.VISIBLE : View.INVISIBLE);
} else {
mChronometer.setVisibility(View.VISIBLE);
}
}
/** /**
* Bind the audio attachment view with a MessagePartData. * Bind the audio attachment view with a MessagePartData.
* @param incoming indicates whether the attachment view is to be styled as a part of an * @param incoming indicates whether the attachment view is to be styled as a part of an
* incoming message. * incoming message.
*/ */
public void bindMessagePartData(final MessagePartData messagePartData, public void bindMessagePartData(final MessagePartData messagePartData,
final boolean incoming) { final boolean incoming, final boolean showAsSelected) {
Assert.isTrue(messagePartData == null || Assert.isTrue(messagePartData == null ||
ContentType.isAudioType(messagePartData.getContentType())); ContentType.isAudioType(messagePartData.getContentType()));
final Uri contentUri = (messagePartData == null) ? null : messagePartData.getContentUri(); final Uri contentUri = (messagePartData == null) ? null : messagePartData.getContentUri();
bind(contentUri, incoming); bind(contentUri, incoming, showAsSelected);
} }
public void bind(final Uri dataSourceUri, final boolean incoming) { public void bind(
final Uri dataSourceUri, final boolean incoming, final boolean showAsSelected) {
final String currentUriString = (mDataSourceUri == null) ? "" : mDataSourceUri.toString(); final String currentUriString = (mDataSourceUri == null) ? "" : mDataSourceUri.toString();
final String newUriString = (dataSourceUri == null) ? "" : dataSourceUri.toString(); final String newUriString = (dataSourceUri == null) ? "" : dataSourceUri.toString();
mShowAsIncoming = incoming; final int themeColor = ConversationDrawables.get().getConversationThemeColor();
final boolean useIncomingStyle = incoming || showAsSelected;
final boolean visualStyleChanged = mThemeColor != themeColor ||
mUseIncomingStyle != useIncomingStyle;
mUseIncomingStyle = useIncomingStyle;
mThemeColor = themeColor;
mPrepareOnPlayback = incoming && !MediaUtil.canAutoAccessIncomingMedia();
if (!TextUtils.equals(currentUriString, newUriString)) { if (!TextUtils.equals(currentUriString, newUriString)) {
mDataSourceUri = dataSourceUri; mDataSourceUri = dataSourceUri;
resetToZeroState(); resetToZeroState();
} else if (visualStyleChanged) {
updateVisualStyle();
} }
} }
@@ -173,11 +215,15 @@ public class AudioAttachmentView extends LinearLayout {
releaseMediaPlayer(); releaseMediaPlayer();
} }
/**
* Prepare the MediaPlayer, and if mPrepareOnPlayback, start playing the audio
*/
private void setupMediaPlayer() { private void setupMediaPlayer() {
Assert.notNull(mDataSourceUri); Assert.notNull(mDataSourceUri);
if (mMediaPlayer == null) { if (mMediaPlayer == null) {
Assert.isTrue(!mPrepared); Assert.isTrue(!mPrepared);
mMediaPlayer = new MediaPlayer(); mMediaPlayer = new MediaPlayer();
try { try {
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDataSource(Factory.get().getApplicationContext(), mDataSourceUri); mMediaPlayer.setDataSource(Factory.get().getApplicationContext(), mDataSourceUri);
@@ -188,6 +234,7 @@ public class AudioAttachmentView extends LinearLayout {
mChronometer.reset(); mChronometer.reset();
mChronometer.setBase(SystemClock.elapsedRealtime() - mChronometer.setBase(SystemClock.elapsedRealtime() -
mMediaPlayer.getDuration()); mMediaPlayer.getDuration());
updateChronometerVisibility(false /* playing */);
mProgressBar.reset(); mProgressBar.reset();
mPlaybackFinished = true; mPlaybackFinished = true;
@@ -203,16 +250,24 @@ public class AudioAttachmentView extends LinearLayout {
mProgressBar.setDuration(mMediaPlayer.getDuration()); mProgressBar.setDuration(mMediaPlayer.getDuration());
mMediaPlayer.seekTo(0); mMediaPlayer.seekTo(0);
mPrepared = true; mPrepared = true;
if (mStartPlayAfterPrepare) {
mStartPlayAfterPrepare = false;
playAudio();
updatePlayPauseButtonState();
}
} }
}); });
mMediaPlayer.setOnErrorListener(new OnErrorListener() { mMediaPlayer.setOnErrorListener(new OnErrorListener() {
@Override @Override
public boolean onError(final MediaPlayer mp, final int what, final int extra) { public boolean onError(final MediaPlayer mp, final int what, final int extra) {
mStartPlayAfterPrepare = false;
onAudioReplayError(what, extra, null); onAudioReplayError(what, extra, null);
return true; return true;
} }
}); });
mMediaPlayer.prepareAsync(); mMediaPlayer.prepareAsync();
} catch (final Exception exception) { } catch (final Exception exception) {
onAudioReplayError(0, 0, exception); onAudioReplayError(0, 0, exception);
@@ -226,13 +281,17 @@ public class AudioAttachmentView extends LinearLayout {
mMediaPlayer.release(); mMediaPlayer.release();
mMediaPlayer = null; mMediaPlayer = null;
mPrepared = false; mPrepared = false;
mStartPlayAfterPrepare = false;
mPlaybackFinished = false; mPlaybackFinished = false;
mChronometer.reset();
mProgressBar.reset();
} }
} }
@Override @Override
protected void onDetachedFromWindow() { protected void onDetachedFromWindow() {
super.onDetachedFromWindow(); super.onDetachedFromWindow();
// The view must have scrolled off. Stop playback.
releaseMediaPlayer(); releaseMediaPlayer();
} }
@@ -258,22 +317,23 @@ public class AudioAttachmentView extends LinearLayout {
} }
private void updatePlayPauseButtonState() { private void updatePlayPauseButtonState() {
if (mMediaPlayer == null || !mMediaPlayer.isPlaying()) { final boolean playing = mMediaPlayer != null && mMediaPlayer.isPlaying();
mPlayPauseButton.setDisplayedChild(PLAY_BUTTON); updateChronometerVisibility(playing);
} else { if (mStartPlayAfterPrepare || playing) {
mPlayPauseButton.setDisplayedChild(PAUSE_BUTTON); mPlayPauseButton.setDisplayedChild(PAUSE_BUTTON);
} else {
mPlayPauseButton.setDisplayedChild(PLAY_BUTTON);
} }
} }
private void resetToZeroState() { private void resetToZeroState() {
// Release the media player so it may be set up with the new audio source. // Release the media player so it may be set up with the new audio source.
releaseMediaPlayer(); releaseMediaPlayer();
mChronometer.reset();
mProgressBar.reset();
updateVisualStyle(); updateVisualStyle();
updateChronometerVisibility(false /* playing */);
if (mDataSourceUri != null) { if (mDataSourceUri != null && !mPrepareOnPlayback) {
// Re-ensure the media player, so we can read the duration of the audio. // Prepare the media player, so we can read the duration of the audio.
setupMediaPlayer(); setupMediaPlayer();
} }
} }
@@ -284,13 +344,13 @@ public class AudioAttachmentView extends LinearLayout {
return; return;
} }
if (mShowAsIncoming) { if (mUseIncomingStyle) {
mChronometer.setTextColor(getResources().getColor(R.color.message_text_color_incoming)); mChronometer.setTextColor(getResources().getColor(R.color.message_text_color_incoming));
} else { } else {
mChronometer.setTextColor(getResources().getColor(R.color.message_text_color_outgoing)); mChronometer.setTextColor(getResources().getColor(R.color.message_text_color_outgoing));
} }
mProgressBar.setVisualStyle(mShowAsIncoming); mProgressBar.setVisualStyle(mUseIncomingStyle);
mPlayPauseButton.setVisualStyle(mShowAsIncoming); mPlayPauseButton.setVisualStyle(mUseIncomingStyle);
updatePlayPauseButtonState(); updatePlayPauseButtonState();
} }
@@ -1110,7 +1110,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
@Override @Override
public void bindView(final View view, final MessagePartData attachment) { public void bindView(final View view, final MessagePartData attachment) {
final AudioAttachmentView audioView = (AudioAttachmentView) view; final AudioAttachmentView audioView = (AudioAttachmentView) view;
audioView.bindMessagePartData(attachment, isSelected() || mData.getIsIncoming()); audioView.bindMessagePartData(attachment, mData.getIsIncoming(), isSelected());
audioView.setBackground(ConversationDrawables.get().getBubbleDrawable( audioView.setBackground(ConversationDrawables.get().getBubbleDrawable(
isSelected(), mData.getIsIncoming(), false /* needArrow */, isSelected(), mData.getIsIncoming(), false /* needArrow */,
mData.hasIncomingErrorStatus())); mData.hasIncomingErrorStatus()));
@@ -1118,7 +1118,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
@Override @Override
public void unbind(final View view) { public void unbind(final View view) {
((AudioAttachmentView) view).bindMessagePartData(null, mData.getIsIncoming()); ((AudioAttachmentView) view).bindMessagePartData(null, mData.getIsIncoming(), false);
} }
}; };
@@ -465,7 +465,8 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
int audioPreviewVisiblity = GONE; int audioPreviewVisiblity = GONE;
if (previewUri != null && !TextUtils.isEmpty(previewContentType)) { if (previewUri != null && !TextUtils.isEmpty(previewContentType)) {
if (ContentType.isAudioType(previewContentType)) { if (ContentType.isAudioType(previewContentType)) {
mAudioAttachmentView.bind(previewUri, false); boolean incoming = !(mData.getShowDraft() || mData.getIsMessageTypeOutgoing());
mAudioAttachmentView.bind(previewUri, incoming, false);
audioPreviewVisiblity = VISIBLE; audioPreviewVisiblity = VISIBLE;
} else if (ContentType.isVideoType(previewContentType)) { } else if (ContentType.isVideoType(previewContentType)) {
previewImageUri = UriUtil.getUriForResourceId( previewImageUri = UriUtil.getUriForResourceId(
@@ -33,4 +33,8 @@ public abstract class MediaUtil {
*/ */
public abstract void playSound(final Context context, final int resId, public abstract void playSound(final Context context, final int resId,
final OnCompletionListener completionListener); final OnCompletionListener completionListener);
public static boolean canAutoAccessIncomingMedia() {
return OsUtil.isAtLeastM();
}
} }