diff options
Diffstat (limited to 'src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java')
-rw-r--r-- | src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java | 1010 |
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; - } -} |