summaryrefslogtreecommitdiff
path: root/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java')
-rw-r--r--src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java1010
1 files changed, 0 insertions, 1010 deletions
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
deleted file mode 100644
index e224ddc2a..000000000
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
+++ /dev/null
@@ -1,1010 +0,0 @@
-/*
- * Copyright (C) 2011 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;
-
-import com.google.common.annotations.VisibleForTesting;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.media.MediaPlayer;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.provider.VoicemailContract;
-import android.support.v4.content.FileProvider;
-import android.util.Log;
-import android.view.WindowManager.LayoutParams;
-
-import com.android.dialer.R;
-import com.android.dialer.calllog.CallLogAsyncTaskUtil;
-import com.android.dialer.util.AsyncTaskExecutor;
-import com.android.dialer.util.AsyncTaskExecutors;
-import com.android.common.io.MoreCloseables;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executors;
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.annotation.concurrent.NotThreadSafe;
-import javax.annotation.concurrent.ThreadSafe;
-
-/**
- * Contains the controlling logic for a voicemail playback in the call log. It is closely coupled
- * to assumptions about the behaviors and lifecycle of the call log, in particular in the
- * {@link CallLogFragment} and {@link CallLogAdapter}.
- * <p>
- * This controls a single {@link com.android.dialer.voicemail.VoicemailPlaybackLayout}. A single
- * instance can be reused for different such layouts, using {@link #setPlaybackView}. This
- * is to facilitate reuse across different voicemail call log entries.
- * <p>
- * This class is not thread safe. The thread policy for this class is thread-confinement, all calls
- * into this class from outside must be done from the main UI thread.
- */
-@NotThreadSafe
-@VisibleForTesting
-public class VoicemailPlaybackPresenter implements MediaPlayer.OnPreparedListener,
- MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener {
-
- private static final String TAG = "VmPlaybackPresenter";
-
- /** Contract describing the behaviour we need from the ui we are controlling. */
- public interface PlaybackView {
- int getDesiredClipPosition();
- void disableUiElements();
- void enableUiElements();
- void onPlaybackError();
- void onPlaybackStarted(int duration, ScheduledExecutorService executorService);
- void onPlaybackStopped();
- void onSpeakerphoneOn(boolean on);
- void setClipPosition(int clipPositionInMillis, int clipLengthInMillis);
- void setSuccess();
- void setFetchContentTimeout();
- void setIsFetchingContent();
- void onVoicemailArchiveSucceded(Uri voicemailUri);
- void onVoicemailArchiveFailed(Uri voicemailUri);
- void setPresenter(VoicemailPlaybackPresenter presenter, Uri voicemailUri);
- void resetSeekBar();
- }
-
- public interface OnVoicemailDeletedListener {
- void onVoicemailDeleted(Uri uri);
- void onVoicemailDeleteUndo();
- void onVoicemailDeletedInDatabase();
- }
-
- /** The enumeration of {@link AsyncTask} objects we use in this class. */
- public enum Tasks {
- CHECK_FOR_CONTENT,
- CHECK_CONTENT_AFTER_CHANGE,
- ARCHIVE_VOICEMAIL
- }
-
- protected interface OnContentCheckedListener {
- void onContentChecked(boolean hasContent);
- }
-
- private static final String[] HAS_CONTENT_PROJECTION = new String[] {
- VoicemailContract.Voicemails.HAS_CONTENT,
- VoicemailContract.Voicemails.DURATION
- };
-
- private static final int NUMBER_OF_THREADS_IN_POOL = 2;
- // Time to wait for content to be fetched before timing out.
- private static final long FETCH_CONTENT_TIMEOUT_MS = 20000;
-
- private static final String VOICEMAIL_URI_KEY =
- VoicemailPlaybackPresenter.class.getName() + ".VOICEMAIL_URI";
- private static final String IS_PREPARED_KEY =
- VoicemailPlaybackPresenter.class.getName() + ".IS_PREPARED";
- // If present in the saved instance bundle, we should not resume playback on create.
- private static final String IS_PLAYING_STATE_KEY =
- VoicemailPlaybackPresenter.class.getName() + ".IS_PLAYING_STATE_KEY";
- // If present in the saved instance bundle, indicates where to set the playback slider.
- private static final String CLIP_POSITION_KEY =
- VoicemailPlaybackPresenter.class.getName() + ".CLIP_POSITION_KEY";
- private static final String IS_SPEAKERPHONE_ON_KEY =
- VoicemailPlaybackPresenter.class.getName() + ".IS_SPEAKER_PHONE_ON";
- public static final int PLAYBACK_REQUEST = 0;
- public static final int ARCHIVE_REQUEST = 1;
- public static final int SHARE_REQUEST = 2;
-
- /**
- * The most recently cached duration. We cache this since we don't want to keep requesting it
- * from the player, as this can easily lead to throwing {@link IllegalStateException} (any time
- * the player is released, it's illegal to ask for the duration).
- */
- private final AtomicInteger mDuration = new AtomicInteger(0);
-
- private static VoicemailPlaybackPresenter sInstance;
-
- private Activity mActivity;
- protected Context mContext;
- private PlaybackView mView;
- protected Uri mVoicemailUri;
-
- protected MediaPlayer mMediaPlayer;
- private int mPosition;
- private boolean mIsPlaying;
- // MediaPlayer crashes on some method calls if not prepared but does not have a method which
- // exposes its prepared state. Store this locally, so we can check and prevent crashes.
- private boolean mIsPrepared;
- private boolean mIsSpeakerphoneOn;
-
- private boolean mShouldResumePlaybackAfterSeeking;
- private int mInitialOrientation;
-
- // Used to run async tasks that need to interact with the UI.
- protected AsyncTaskExecutor mAsyncTaskExecutor;
- private static ScheduledExecutorService mScheduledExecutorService;
- /**
- * Used to handle the result of a successful or time-out fetch result.
- * <p>
- * This variable is thread-contained, accessed only on the ui thread.
- */
- private FetchResultHandler mFetchResultHandler;
- private final List<FetchResultHandler> mArchiveResultHandlers = new ArrayList<>();
- private Handler mHandler = new Handler();
- private PowerManager.WakeLock mProximityWakeLock;
- private VoicemailAudioManager mVoicemailAudioManager;
-
- private OnVoicemailDeletedListener mOnVoicemailDeletedListener;
- private final VoicemailAsyncTaskUtil mVoicemailAsyncTaskUtil;
-
- /**
- * Obtain singleton instance of this class. Use a single instance to provide a consistent
- * listener to the AudioManager when requesting and abandoning audio focus.
- *
- * Otherwise, after rotation the previous listener will still be active but a new listener
- * will be provided to calls to the AudioManager, which is bad. For example, abandoning
- * audio focus with the new listeners results in an AUDIO_FOCUS_GAIN callback to the
- * previous listener, which is the opposite of the intended behavior.
- */
- public static VoicemailPlaybackPresenter getInstance(
- Activity activity, Bundle savedInstanceState) {
- if (sInstance == null) {
- sInstance = new VoicemailPlaybackPresenter(activity);
- }
-
- sInstance.init(activity, savedInstanceState);
- return sInstance;
- }
-
- /**
- * Initialize variables which are activity-independent and state-independent.
- */
- protected VoicemailPlaybackPresenter(Activity activity) {
- Context context = activity.getApplicationContext();
- mAsyncTaskExecutor = AsyncTaskExecutors.createAsyncTaskExecutor();
- mVoicemailAudioManager = new VoicemailAudioManager(context, this);
- mVoicemailAsyncTaskUtil = new VoicemailAsyncTaskUtil(context.getContentResolver());
- PowerManager powerManager =
- (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- if (powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
- mProximityWakeLock = powerManager.newWakeLock(
- PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
- }
- }
-
- /**
- * Update variables which are activity-dependent or state-dependent.
- */
- protected void init(Activity activity, Bundle savedInstanceState) {
- mActivity = activity;
- mContext = activity;
-
- mInitialOrientation = mContext.getResources().getConfiguration().orientation;
- mActivity.setVolumeControlStream(VoicemailAudioManager.PLAYBACK_STREAM);
-
- if (savedInstanceState != null) {
- // Restores playback state when activity is recreated, such as after rotation.
- mVoicemailUri = (Uri) savedInstanceState.getParcelable(VOICEMAIL_URI_KEY);
- mIsPrepared = savedInstanceState.getBoolean(IS_PREPARED_KEY);
- mPosition = savedInstanceState.getInt(CLIP_POSITION_KEY, 0);
- mIsPlaying = savedInstanceState.getBoolean(IS_PLAYING_STATE_KEY, false);
- mIsSpeakerphoneOn = savedInstanceState.getBoolean(IS_SPEAKERPHONE_ON_KEY, false);
- }
-
- if (mMediaPlayer == null) {
- mIsPrepared = false;
- mIsPlaying = false;
- }
- }
-
- /**
- * Must be invoked when the parent Activity is saving it state.
- */
- public void onSaveInstanceState(Bundle outState) {
- if (mView != null) {
- outState.putParcelable(VOICEMAIL_URI_KEY, mVoicemailUri);
- outState.putBoolean(IS_PREPARED_KEY, mIsPrepared);
- outState.putInt(CLIP_POSITION_KEY, mView.getDesiredClipPosition());
- outState.putBoolean(IS_PLAYING_STATE_KEY, mIsPlaying);
- outState.putBoolean(IS_SPEAKERPHONE_ON_KEY, mIsSpeakerphoneOn);
- }
- }
-
- /**
- * Specify the view which this presenter controls and the voicemail to prepare to play.
- */
- public void setPlaybackView(
- PlaybackView view, Uri voicemailUri, boolean startPlayingImmediately) {
- mView = view;
- mView.setPresenter(this, voicemailUri);
-
- // Handles cases where the same entry is binded again when scrolling in list, or where
- // the MediaPlayer was retained after an orientation change.
- if (mMediaPlayer != null && mIsPrepared && voicemailUri.equals(mVoicemailUri)) {
- // If the voicemail card was rebinded, we need to set the position to the appropriate
- // point. Since we retain the media player, we can just set it to the position of the
- // media player.
- mPosition = mMediaPlayer.getCurrentPosition();
- onPrepared(mMediaPlayer);
- } else {
- if (!voicemailUri.equals(mVoicemailUri)) {
- mVoicemailUri = voicemailUri;
- mPosition = 0;
- // Default to earpiece.
- setSpeakerphoneOn(false);
- mVoicemailAudioManager.setSpeakerphoneOn(false);
- } else {
- // Update the view to the current speakerphone state.
- mView.onSpeakerphoneOn(mIsSpeakerphoneOn);
- }
- /*
- * Check to see if the content field in the DB is set. If set, we proceed to
- * prepareContent() method. We get the duration of the voicemail from the query and set
- * it if the content is not available.
- */
- checkForContent(new OnContentCheckedListener() {
- @Override
- public void onContentChecked(boolean hasContent) {
- if (hasContent) {
- prepareContent();
- } else if (mView != null) {
- mView.resetSeekBar();
- mView.setClipPosition(0, mDuration.get());
- }
- }
- });
-
- if (startPlayingImmediately) {
- // Since setPlaybackView can get called during the view binding process, we don't
- // want to reset mIsPlaying to false if the user is currently playing the
- // voicemail and the view is rebound.
- mIsPlaying = startPlayingImmediately;
- }
- }
- }
-
- /**
- * Reset the presenter for playback back to its original state.
- */
- public void resetAll() {
- pausePresenter(true);
-
- mView = null;
- mVoicemailUri = null;
- }
-
- /**
- * When navigating away from voicemail playback, we need to release the media player,
- * pause the UI and save the position.
- *
- * @param reset {@code true} if we want to reset the position of the playback, {@code false} if
- * we want to retain the current position (in case we return to the voicemail).
- */
- public void pausePresenter(boolean reset) {
- if (mMediaPlayer != null) {
- mMediaPlayer.release();
- mMediaPlayer = null;
- }
-
- disableProximitySensor(false /* waitForFarState */);
-
- mIsPrepared = false;
- mIsPlaying = false;
-
- if (reset) {
- // We want to reset the position whether or not the view is valid.
- mPosition = 0;
- }
-
- if (mView != null) {
- mView.onPlaybackStopped();
- if (reset) {
- mView.setClipPosition(0, mDuration.get());
- } else {
- mPosition = mView.getDesiredClipPosition();
- }
- }
- }
-
- /**
- * Must be invoked when the parent activity is resumed.
- */
- public void onResume() {
- mVoicemailAudioManager.registerReceivers();
- }
-
- /**
- * Must be invoked when the parent activity is paused.
- */
- public void onPause() {
- mVoicemailAudioManager.unregisterReceivers();
-
- if (mContext != null && mIsPrepared
- && mInitialOrientation != mContext.getResources().getConfiguration().orientation) {
- // If an orientation change triggers the pause, retain the MediaPlayer.
- Log.d(TAG, "onPause: Orientation changed.");
- return;
- }
-
- // Release the media player, otherwise there may be failures.
- pausePresenter(false);
-
- if (mActivity != null) {
- mActivity.getWindow().clearFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
-
- }
-
- /**
- * Must be invoked when the parent activity is destroyed.
- */
- public void onDestroy() {
- // Clear references to avoid leaks from the singleton instance.
- mActivity = null;
- mContext = null;
-
- if (mScheduledExecutorService != null) {
- mScheduledExecutorService.shutdown();
- mScheduledExecutorService = null;
- }
-
- if (!mArchiveResultHandlers.isEmpty()) {
- for (FetchResultHandler fetchResultHandler : mArchiveResultHandlers) {
- fetchResultHandler.destroy();
- }
- mArchiveResultHandlers.clear();
- }
-
- if (mFetchResultHandler != null) {
- mFetchResultHandler.destroy();
- mFetchResultHandler = null;
- }
- }
-
- /**
- * Checks to see if we have content available for this voicemail.
- */
- protected void checkForContent(final OnContentCheckedListener callback) {
- mAsyncTaskExecutor.submit(Tasks.CHECK_FOR_CONTENT, new AsyncTask<Void, Void, Boolean>() {
- @Override
- public Boolean doInBackground(Void... params) {
- return queryHasContent(mVoicemailUri);
- }
-
- @Override
- public void onPostExecute(Boolean hasContent) {
- callback.onContentChecked(hasContent);
- }
- });
- }
-
- private boolean queryHasContent(Uri voicemailUri) {
- if (voicemailUri == null || mContext == null) {
- return false;
- }
-
- ContentResolver contentResolver = mContext.getContentResolver();
- Cursor cursor = contentResolver.query(
- voicemailUri, null, null, null, null);
- try {
- if (cursor != null && cursor.moveToNext()) {
- int duration = cursor.getInt(cursor.getColumnIndex(
- VoicemailContract.Voicemails.DURATION));
- // Convert database duration (seconds) into mDuration (milliseconds)
- mDuration.set(duration > 0 ? duration * 1000 : 0);
- return cursor.getInt(cursor.getColumnIndex(
- VoicemailContract.Voicemails.HAS_CONTENT)) == 1;
- }
- } finally {
- MoreCloseables.closeQuietly(cursor);
- }
- return false;
- }
-
- /**
- * Makes a broadcast request to ask that a voicemail source fetch this content.
- * <p>
- * This method <b>must be called on the ui thread</b>.
- * <p>
- * This method will be called when we realise that we don't have content for this voicemail. It
- * will trigger a broadcast to request that the content be downloaded. It will add a listener to
- * the content resolver so that it will be notified when the has_content field changes. It will
- * also set a timer. If the has_content field changes to true within the allowed time, we will
- * proceed to {@link #prepareContent()}. If the has_content field does not
- * become true within the allowed time, we will update the ui to reflect the fact that content
- * was not available.
- *
- * @return whether issued request to fetch content
- */
- protected boolean requestContent(int code) {
- if (mContext == null || mVoicemailUri == null) {
- return false;
- }
-
- FetchResultHandler tempFetchResultHandler =
- new FetchResultHandler(new Handler(), mVoicemailUri, code);
-
- switch (code) {
- case ARCHIVE_REQUEST:
- mArchiveResultHandlers.add(tempFetchResultHandler);
- break;
- default:
- if (mFetchResultHandler != null) {
- mFetchResultHandler.destroy();
- }
- mView.setIsFetchingContent();
- mFetchResultHandler = tempFetchResultHandler;
- break;
- }
-
- // Send voicemail fetch request.
- Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, mVoicemailUri);
- mContext.sendBroadcast(intent);
- return true;
- }
-
- @ThreadSafe
- private class FetchResultHandler extends ContentObserver implements Runnable {
- private AtomicBoolean mIsWaitingForResult = new AtomicBoolean(true);
- private final Handler mFetchResultHandler;
- private final Uri mVoicemailUri;
- private final int mRequestCode;
-
- public FetchResultHandler(Handler handler, Uri uri, int code) {
- super(handler);
- mFetchResultHandler = handler;
- mRequestCode = code;
- mVoicemailUri = uri;
- if (mContext != null) {
- mContext.getContentResolver().registerContentObserver(
- mVoicemailUri, false, this);
- mFetchResultHandler.postDelayed(this, FETCH_CONTENT_TIMEOUT_MS);
- }
- }
-
- /**
- * Stop waiting for content and notify UI if {@link FETCH_CONTENT_TIMEOUT_MS} has elapsed.
- */
- @Override
- public void run() {
- if (mIsWaitingForResult.getAndSet(false) && mContext != null) {
- mContext.getContentResolver().unregisterContentObserver(this);
- if (mView != null) {
- mView.setFetchContentTimeout();
- }
- }
- }
-
- public void destroy() {
- if (mIsWaitingForResult.getAndSet(false) && mContext != null) {
- mContext.getContentResolver().unregisterContentObserver(this);
- mFetchResultHandler.removeCallbacks(this);
- }
- }
-
- @Override
- public void onChange(boolean selfChange) {
- mAsyncTaskExecutor.submit(Tasks.CHECK_CONTENT_AFTER_CHANGE,
- new AsyncTask<Void, Void, Boolean>() {
-
- @Override
- public Boolean doInBackground(Void... params) {
- return queryHasContent(mVoicemailUri);
- }
-
- @Override
- public void onPostExecute(Boolean hasContent) {
- if (hasContent && mContext != null && mIsWaitingForResult.getAndSet(false)) {
- mContext.getContentResolver().unregisterContentObserver(
- FetchResultHandler.this);
- prepareContent();
- if (mRequestCode == ARCHIVE_REQUEST) {
- startArchiveVoicemailTask(mVoicemailUri, true /* archivedByUser */);
- } else if (mRequestCode == SHARE_REQUEST) {
- startArchiveVoicemailTask(mVoicemailUri, false /* archivedByUser */);
- }
- }
- }
- });
- }
- }
-
- /**
- * Prepares the voicemail content for playback.
- * <p>
- * This method will be called once we know that our voicemail has content (according to the
- * content provider). this method asynchronously tries to prepare the data source through the
- * media player. If preparation is successful, the media player will {@link #onPrepared()},
- * and it will call {@link #onError()} otherwise.
- */
- protected void prepareContent() {
- if (mView == null) {
- return;
- }
- Log.d(TAG, "prepareContent");
-
- // Release the previous media player, otherwise there may be failures.
- if (mMediaPlayer != null) {
- mMediaPlayer.release();
- mMediaPlayer = null;
- }
-
- mView.disableUiElements();
- mIsPrepared = false;
-
- try {
- mMediaPlayer = new MediaPlayer();
- mMediaPlayer.setOnPreparedListener(this);
- mMediaPlayer.setOnErrorListener(this);
- mMediaPlayer.setOnCompletionListener(this);
-
- mMediaPlayer.reset();
- mMediaPlayer.setDataSource(mContext, mVoicemailUri);
- mMediaPlayer.setAudioStreamType(VoicemailAudioManager.PLAYBACK_STREAM);
- mMediaPlayer.prepareAsync();
- } catch (IOException e) {
- handleError(e);
- }
- }
-
- /**
- * Once the media player is prepared, enables the UI and adopts the appropriate playback state.
- */
- @Override
- public void onPrepared(MediaPlayer mp) {
- if (mView == null) {
- return;
- }
- Log.d(TAG, "onPrepared");
- mIsPrepared = true;
-
- // Update the duration in the database if it was not previously retrieved
- CallLogAsyncTaskUtil.updateVoicemailDuration(mContext, mVoicemailUri,
- TimeUnit.MILLISECONDS.toSeconds(mMediaPlayer.getDuration()));
-
- mDuration.set(mMediaPlayer.getDuration());
-
- Log.d(TAG, "onPrepared: mPosition=" + mPosition);
- mView.setClipPosition(mPosition, mDuration.get());
- mView.enableUiElements();
- mView.setSuccess();
- mMediaPlayer.seekTo(mPosition);
-
- if (mIsPlaying) {
- resumePlayback();
- } else {
- pausePlayback();
- }
- }
-
- /**
- * Invoked if preparing the media player fails, for example, if file is missing or the voicemail
- * is an unknown file format that can't be played.
- */
- @Override
- public boolean onError(MediaPlayer mp, int what, int extra) {
- handleError(new IllegalStateException("MediaPlayer error listener invoked: " + extra));
- return true;
- }
-
- protected void handleError(Exception e) {
- Log.d(TAG, "handleError: Could not play voicemail " + e);
-
- if (mIsPrepared) {
- mMediaPlayer.release();
- mMediaPlayer = null;
- mIsPrepared = false;
- }
-
- if (mView != null) {
- mView.onPlaybackError();
- }
-
- mPosition = 0;
- mIsPlaying = false;
- }
-
- /**
- * After done playing the voicemail clip, reset the clip position to the start.
- */
- @Override
- public void onCompletion(MediaPlayer mediaPlayer) {
- pausePlayback();
-
- // Reset the seekbar position to the beginning.
- mPosition = 0;
- if (mView != null) {
- mView.setClipPosition(0, mDuration.get());
- }
- }
-
- /**
- * Only play voicemail when audio focus is granted. When it is lost (usually by another
- * application requesting focus), pause playback.
- *
- * @param gainedFocus {@code true} if the audio focus was gained, {@code} false otherwise.
- */
- public void onAudioFocusChange(boolean gainedFocus) {
- if (mIsPlaying == gainedFocus) {
- // Nothing new here, just exit.
- return;
- }
-
- if (!mIsPlaying) {
- resumePlayback();
- } else {
- pausePlayback();
- }
- }
-
- /**
- * Resumes voicemail playback at the clip position stored by the presenter. Null-op if already
- * playing.
- */
- public void resumePlayback() {
- if (mView == null) {
- return;
- }
-
- if (!mIsPrepared) {
- /*
- * Check content before requesting content to avoid duplicated requests. It is possible
- * that the UI doesn't know content has arrived if the fetch took too long causing a
- * timeout, but succeeded.
- */
- checkForContent(new OnContentCheckedListener() {
- @Override
- public void onContentChecked(boolean hasContent) {
- if (!hasContent) {
- // No local content, download from server. Queue playing if the request was
- // issued,
- mIsPlaying = requestContent(PLAYBACK_REQUEST);
- } else {
- // Queue playing once the media play loaded the content.
- mIsPlaying = true;
- prepareContent();
- }
- }
- });
- return;
- }
-
- mIsPlaying = true;
-
- if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
- // Clamp the start position between 0 and the duration.
- mPosition = Math.max(0, Math.min(mPosition, mDuration.get()));
-
- mMediaPlayer.seekTo(mPosition);
-
- try {
- // Grab audio focus.
- // Can throw RejectedExecutionException.
- mVoicemailAudioManager.requestAudioFocus();
- mMediaPlayer.start();
- setSpeakerphoneOn(mIsSpeakerphoneOn);
- } catch (RejectedExecutionException e) {
- handleError(e);
- }
- }
-
- Log.d(TAG, "Resumed playback at " + mPosition + ".");
- mView.onPlaybackStarted(mDuration.get(), getScheduledExecutorServiceInstance());
- }
-
- /**
- * Pauses voicemail playback at the current position. Null-op if already paused.
- */
- public void pausePlayback() {
- if (!mIsPrepared) {
- return;
- }
-
- mIsPlaying = false;
-
- if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
- mMediaPlayer.pause();
- }
-
- mPosition = mMediaPlayer == null ? 0 : mMediaPlayer.getCurrentPosition();
-
- Log.d(TAG, "Paused playback at " + mPosition + ".");
-
- if (mView != null) {
- mView.onPlaybackStopped();
- }
-
- mVoicemailAudioManager.abandonAudioFocus();
-
- if (mActivity != null) {
- mActivity.getWindow().clearFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
- disableProximitySensor(true /* waitForFarState */);
- }
-
- /**
- * Pauses playback when the user starts seeking the position, and notes whether the voicemail is
- * playing to know whether to resume playback once the user selects a new position.
- */
- public void pausePlaybackForSeeking() {
- if (mMediaPlayer != null) {
- mShouldResumePlaybackAfterSeeking = mMediaPlayer.isPlaying();
- }
- pausePlayback();
- }
-
- public void resumePlaybackAfterSeeking(int desiredPosition) {
- mPosition = desiredPosition;
- if (mShouldResumePlaybackAfterSeeking) {
- mShouldResumePlaybackAfterSeeking = false;
- resumePlayback();
- }
- }
-
- /**
- * Seek to position. This is called when user manually seek the playback. It could be either
- * by touch or volume button while in talkback mode.
- * @param position
- */
- public void seek(int position) {
- mPosition = position;
- }
-
- private void enableProximitySensor() {
- if (mProximityWakeLock == null || mIsSpeakerphoneOn || !mIsPrepared
- || mMediaPlayer == null || !mMediaPlayer.isPlaying()) {
- return;
- }
-
- if (!mProximityWakeLock.isHeld()) {
- Log.i(TAG, "Acquiring proximity wake lock");
- mProximityWakeLock.acquire();
- } else {
- Log.i(TAG, "Proximity wake lock already acquired");
- }
- }
-
- private void disableProximitySensor(boolean waitForFarState) {
- if (mProximityWakeLock == null) {
- return;
- }
- if (mProximityWakeLock.isHeld()) {
- Log.i(TAG, "Releasing proximity wake lock");
- int flags = waitForFarState ? PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY : 0;
- mProximityWakeLock.release(flags);
- } else {
- Log.i(TAG, "Proximity wake lock already released");
- }
- }
-
- /**
- * This is for use by UI interactions only. It simplifies UI logic.
- */
- public void toggleSpeakerphone() {
- mVoicemailAudioManager.setSpeakerphoneOn(!mIsSpeakerphoneOn);
- setSpeakerphoneOn(!mIsSpeakerphoneOn);
- }
-
- /**
- * This method only handles app-level changes to the speakerphone. Audio layer changes should
- * be handled separately. This is so that the VoicemailAudioManager can trigger changes to
- * the presenter without the presenter triggering the audio manager and duplicating actions.
- */
- public void setSpeakerphoneOn(boolean on) {
- if (mView == null) {
- return;
- }
-
- mView.onSpeakerphoneOn(on);
-
- mIsSpeakerphoneOn = on;
-
- // This should run even if speakerphone is not being toggled because we may be switching
- // from earpiece to headphone and vise versa. Also upon initial setup the default audio
- // source is the earpiece, so we want to trigger the proximity sensor.
- if (mIsPlaying) {
- if (on || mVoicemailAudioManager.isWiredHeadsetPluggedIn()) {
- disableProximitySensor(false /* waitForFarState */);
- if (mIsPrepared && mMediaPlayer != null && mMediaPlayer.isPlaying()) {
- mActivity.getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
- } else {
- enableProximitySensor();
- if (mActivity != null) {
- mActivity.getWindow().clearFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
- }
- }
- }
-
- public void setOnVoicemailDeletedListener(OnVoicemailDeletedListener listener) {
- mOnVoicemailDeletedListener = listener;
- }
-
- public int getMediaPlayerPosition() {
- return mIsPrepared && mMediaPlayer != null ? mMediaPlayer.getCurrentPosition() : 0;
- }
-
- public void notifyUiOfArchiveResult(Uri voicemailUri, boolean archived) {
- if (mView == null) {
- return;
- }
- if (archived) {
- mView.onVoicemailArchiveSucceded(voicemailUri);
- } else {
- mView.onVoicemailArchiveFailed(voicemailUri);
- }
- }
-
- /* package */ void onVoicemailDeleted() {
- // Trampoline the event notification to the interested listener.
- if (mOnVoicemailDeletedListener != null) {
- mOnVoicemailDeletedListener.onVoicemailDeleted(mVoicemailUri);
- }
- }
-
- /* package */ void onVoicemailDeleteUndo() {
- // Trampoline the event notification to the interested listener.
- if (mOnVoicemailDeletedListener != null) {
- mOnVoicemailDeletedListener.onVoicemailDeleteUndo();
- }
- }
-
- /* package */ void onVoicemailDeletedInDatabase() {
- // Trampoline the event notification to the interested listener.
- if (mOnVoicemailDeletedListener != null) {
- mOnVoicemailDeletedListener.onVoicemailDeletedInDatabase();
- }
- }
-
- private static synchronized ScheduledExecutorService getScheduledExecutorServiceInstance() {
- if (mScheduledExecutorService == null) {
- mScheduledExecutorService = Executors.newScheduledThreadPool(NUMBER_OF_THREADS_IN_POOL);
- }
- return mScheduledExecutorService;
- }
-
- /**
- * If voicemail has already been downloaded, go straight to archiving. Otherwise, request
- * the voicemail content first.
- */
- public void archiveContent(final Uri voicemailUri, final boolean archivedByUser) {
- checkForContent(new OnContentCheckedListener() {
- @Override
- public void onContentChecked(boolean hasContent) {
- if (!hasContent) {
- requestContent(archivedByUser ? ARCHIVE_REQUEST : SHARE_REQUEST);
- } else {
- startArchiveVoicemailTask(voicemailUri, archivedByUser);
- }
- }
- });
- }
-
- /**
- * Asynchronous task used to archive a voicemail given its uri.
- */
- protected void startArchiveVoicemailTask(final Uri voicemailUri, final boolean archivedByUser) {
- mVoicemailAsyncTaskUtil.archiveVoicemailContent(
- new VoicemailAsyncTaskUtil.OnArchiveVoicemailListener() {
- @Override
- public void onArchiveVoicemail(final Uri archivedVoicemailUri) {
- if (archivedVoicemailUri == null) {
- notifyUiOfArchiveResult(voicemailUri, false);
- return;
- }
-
- if (archivedByUser) {
- setArchivedVoicemailStatusAndUpdateUI(voicemailUri,
- archivedVoicemailUri, true);
- } else {
- sendShareIntent(archivedVoicemailUri);
- }
- }
- }, voicemailUri);
- }
-
- /**
- * Sends the intent for sharing the voicemail file.
- */
- protected void sendShareIntent(final Uri voicemailUri) {
- mVoicemailAsyncTaskUtil.getVoicemailFilePath(
- new VoicemailAsyncTaskUtil.OnGetArchivedVoicemailFilePathListener() {
- @Override
- public void onGetArchivedVoicemailFilePath(String filePath) {
- mView.enableUiElements();
- if (filePath == null) {
- mView.setFetchContentTimeout();
- return;
- }
- Uri voicemailFileUri = FileProvider.getUriForFile(
- mContext,
- mContext.getString(R.string.contacts_file_provider_authority),
- new File(filePath));
- mContext.startActivity(Intent.createChooser(
- getShareIntent(voicemailFileUri),
- mContext.getResources().getText(
- R.string.call_log_share_voicemail)));
- }
- }, voicemailUri);
- }
-
- /** Sets archived_by_user field to the given boolean and updates the URI. */
- private void setArchivedVoicemailStatusAndUpdateUI(
- final Uri voicemailUri,
- final Uri archivedVoicemailUri,
- boolean status) {
- mVoicemailAsyncTaskUtil.setVoicemailArchiveStatus(
- new VoicemailAsyncTaskUtil.OnSetVoicemailArchiveStatusListener() {
- @Override
- public void onSetVoicemailArchiveStatus(boolean success) {
- notifyUiOfArchiveResult(voicemailUri, success);
- }
- }, archivedVoicemailUri, status);
- }
-
- private Intent getShareIntent(Uri voicemailFileUri) {
- Intent shareIntent = new Intent();
- shareIntent.setAction(Intent.ACTION_SEND);
- shareIntent.putExtra(Intent.EXTRA_STREAM, voicemailFileUri);
- shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- shareIntent.setType(mContext.getContentResolver()
- .getType(voicemailFileUri));
- return shareIntent;
- }
-
- @VisibleForTesting
- public boolean isPlaying() {
- return mIsPlaying;
- }
-
- @VisibleForTesting
- public boolean isSpeakerphoneOn() {
- return mIsSpeakerphoneOn;
- }
-
- @VisibleForTesting
- public void clearInstance() {
- sInstance = null;
- }
-}