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
diff --git a/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java b/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java
index f17591c..219e0a6 100644
--- a/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java
+++ b/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java
@@ -23,6 +23,7 @@
import com.android.messaging.Factory;
import com.android.messaging.util.MediaMetadataRetrieverWrapper;
+import com.android.messaging.util.MediaUtil;
import com.android.messaging.util.OsUtil;
import java.io.FileNotFoundException;
@@ -41,7 +42,7 @@
}
public static boolean shouldShowIncomingVideoThumbnails() {
- return OsUtil.isAtLeastM();
+ return MediaUtil.canAutoAccessIncomingMedia();
}
@Override
diff --git a/src/com/android/messaging/ui/AttachmentPreviewFactory.java b/src/com/android/messaging/ui/AttachmentPreviewFactory.java
index ed5d4d7..7f1fce8 100644
--- a/src/com/android/messaging/ui/AttachmentPreviewFactory.java
+++ b/src/com/android/messaging/ui/AttachmentPreviewFactory.java
@@ -269,7 +269,8 @@
final View view = layoutInflater.inflate(layoutId, parent, false /* attachToRoot */);
final AudioAttachmentView audioView = (AudioAttachmentView)
view.findViewById(R.id.audio_attachment_view);
- audioView.bindMessagePartData(attachmentData, false /* incoming */);
+ audioView.bindMessagePartData(
+ attachmentData, false /* incoming */, false /* showAsSelected */);
return view;
}
diff --git a/src/com/android/messaging/ui/AudioAttachmentView.java b/src/com/android/messaging/ui/AudioAttachmentView.java
index bb649b0..ad91ed2 100644
--- a/src/com/android/messaging/ui/AudioAttachmentView.java
+++ b/src/com/android/messaging/ui/AudioAttachmentView.java
@@ -41,6 +41,7 @@
import com.android.messaging.util.Assert;
import com.android.messaging.util.ContentType;
import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.MediaUtil;
import com.android.messaging.util.UiUtils;
/**
@@ -74,11 +75,15 @@
private int mClipPathWidth;
private int mClipPathHeight;
- // Indicates whether the attachment view is to be styled as a part of an incoming message.
- private boolean mShowAsIncoming;
+ private boolean mUseIncomingStyle;
+ 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 mPlaybackFinished;
+ private boolean mPlaybackFinished; // Was the audio played all the way to the end
private final int mMode;
public AudioAttachmentView(final Context context, final AttributeSet attrs) {
@@ -108,7 +113,7 @@
mPlayPauseButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
- setupMediaPlayer();
+ // Has the MediaPlayer already been prepared?
if (mMediaPlayer != null && mPrepared) {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
@@ -117,6 +122,17 @@
} else {
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();
}
@@ -125,26 +141,52 @@
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.
* @param incoming indicates whether the attachment view is to be styled as a part of an
* incoming message.
*/
public void bindMessagePartData(final MessagePartData messagePartData,
- final boolean incoming) {
+ final boolean incoming, final boolean showAsSelected) {
Assert.isTrue(messagePartData == null ||
ContentType.isAudioType(messagePartData.getContentType()));
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 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)) {
mDataSourceUri = dataSourceUri;
resetToZeroState();
+ } else if (visualStyleChanged) {
+ updateVisualStyle();
}
}
@@ -173,11 +215,15 @@
releaseMediaPlayer();
}
+ /**
+ * Prepare the MediaPlayer, and if mPrepareOnPlayback, start playing the audio
+ */
private void setupMediaPlayer() {
Assert.notNull(mDataSourceUri);
if (mMediaPlayer == null) {
Assert.isTrue(!mPrepared);
mMediaPlayer = new MediaPlayer();
+
try {
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDataSource(Factory.get().getApplicationContext(), mDataSourceUri);
@@ -188,6 +234,7 @@
mChronometer.reset();
mChronometer.setBase(SystemClock.elapsedRealtime() -
mMediaPlayer.getDuration());
+ updateChronometerVisibility(false /* playing */);
mProgressBar.reset();
mPlaybackFinished = true;
@@ -203,16 +250,24 @@
mProgressBar.setDuration(mMediaPlayer.getDuration());
mMediaPlayer.seekTo(0);
mPrepared = true;
+
+ if (mStartPlayAfterPrepare) {
+ mStartPlayAfterPrepare = false;
+ playAudio();
+ updatePlayPauseButtonState();
+ }
}
});
mMediaPlayer.setOnErrorListener(new OnErrorListener() {
@Override
public boolean onError(final MediaPlayer mp, final int what, final int extra) {
+ mStartPlayAfterPrepare = false;
onAudioReplayError(what, extra, null);
return true;
}
});
+
mMediaPlayer.prepareAsync();
} catch (final Exception exception) {
onAudioReplayError(0, 0, exception);
@@ -226,13 +281,17 @@
mMediaPlayer.release();
mMediaPlayer = null;
mPrepared = false;
+ mStartPlayAfterPrepare = false;
mPlaybackFinished = false;
+ mChronometer.reset();
+ mProgressBar.reset();
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
+ // The view must have scrolled off. Stop playback.
releaseMediaPlayer();
}
@@ -258,22 +317,23 @@
}
private void updatePlayPauseButtonState() {
- if (mMediaPlayer == null || !mMediaPlayer.isPlaying()) {
- mPlayPauseButton.setDisplayedChild(PLAY_BUTTON);
- } else {
+ final boolean playing = mMediaPlayer != null && mMediaPlayer.isPlaying();
+ updateChronometerVisibility(playing);
+ if (mStartPlayAfterPrepare || playing) {
mPlayPauseButton.setDisplayedChild(PAUSE_BUTTON);
+ } else {
+ mPlayPauseButton.setDisplayedChild(PLAY_BUTTON);
}
}
private void resetToZeroState() {
// Release the media player so it may be set up with the new audio source.
releaseMediaPlayer();
- mChronometer.reset();
- mProgressBar.reset();
updateVisualStyle();
+ updateChronometerVisibility(false /* playing */);
- if (mDataSourceUri != null) {
- // Re-ensure the media player, so we can read the duration of the audio.
+ if (mDataSourceUri != null && !mPrepareOnPlayback) {
+ // Prepare the media player, so we can read the duration of the audio.
setupMediaPlayer();
}
}
@@ -284,13 +344,13 @@
return;
}
- if (mShowAsIncoming) {
+ if (mUseIncomingStyle) {
mChronometer.setTextColor(getResources().getColor(R.color.message_text_color_incoming));
} else {
mChronometer.setTextColor(getResources().getColor(R.color.message_text_color_outgoing));
}
- mProgressBar.setVisualStyle(mShowAsIncoming);
- mPlayPauseButton.setVisualStyle(mShowAsIncoming);
+ mProgressBar.setVisualStyle(mUseIncomingStyle);
+ mPlayPauseButton.setVisualStyle(mUseIncomingStyle);
updatePlayPauseButtonState();
}
diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageView.java b/src/com/android/messaging/ui/conversation/ConversationMessageView.java
index e22e2c7..0bfb960 100644
--- a/src/com/android/messaging/ui/conversation/ConversationMessageView.java
+++ b/src/com/android/messaging/ui/conversation/ConversationMessageView.java
@@ -1110,7 +1110,7 @@
@Override
public void bindView(final View view, final MessagePartData attachment) {
final AudioAttachmentView audioView = (AudioAttachmentView) view;
- audioView.bindMessagePartData(attachment, isSelected() || mData.getIsIncoming());
+ audioView.bindMessagePartData(attachment, mData.getIsIncoming(), isSelected());
audioView.setBackground(ConversationDrawables.get().getBubbleDrawable(
isSelected(), mData.getIsIncoming(), false /* needArrow */,
mData.hasIncomingErrorStatus()));
@@ -1118,7 +1118,7 @@
@Override
public void unbind(final View view) {
- ((AudioAttachmentView) view).bindMessagePartData(null, mData.getIsIncoming());
+ ((AudioAttachmentView) view).bindMessagePartData(null, mData.getIsIncoming(), false);
}
};
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java b/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java
index 7525182..9b8c5ff 100644
--- a/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java
+++ b/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java
@@ -465,7 +465,8 @@
int audioPreviewVisiblity = GONE;
if (previewUri != null && !TextUtils.isEmpty(previewContentType)) {
if (ContentType.isAudioType(previewContentType)) {
- mAudioAttachmentView.bind(previewUri, false);
+ boolean incoming = !(mData.getShowDraft() || mData.getIsMessageTypeOutgoing());
+ mAudioAttachmentView.bind(previewUri, incoming, false);
audioPreviewVisiblity = VISIBLE;
} else if (ContentType.isVideoType(previewContentType)) {
previewImageUri = UriUtil.getUriForResourceId(
diff --git a/src/com/android/messaging/util/MediaUtil.java b/src/com/android/messaging/util/MediaUtil.java
index f25354c..c23ded3 100644
--- a/src/com/android/messaging/util/MediaUtil.java
+++ b/src/com/android/messaging/util/MediaUtil.java
@@ -33,4 +33,8 @@
*/
public abstract void playSound(final Context context, final int resId,
final OnCompletionListener completionListener);
+
+ public static boolean canAutoAccessIncomingMedia() {
+ return OsUtil.isAtLeastM();
+ }
}