Merge "Import translations. DO NOT MERGE" into klp-dev
diff --git a/res/layout/dialtacts_activity.xml b/res/layout/dialtacts_activity.xml
index 21f84bb..8f9f39c 100644
--- a/res/layout/dialtacts_activity.xml
+++ b/res/layout/dialtacts_activity.xml
@@ -39,6 +39,7 @@
             <FrameLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:id="@+id/search_and_remove_view_container"
                 >
                 <LinearLayout
                     android:layout_width="match_parent"
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index 4542137..c510355 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -168,6 +168,9 @@
     private boolean mFirstLaunch;
     private View mSearchViewContainer;
     private RemoveView mRemoveViewContainer;
+    // This view points to the Framelayout that houses both the search view and remove view
+    // containers.
+    private View mSearchAndRemoveViewContainer;
     private View mSearchViewCloseButton;
     private View mVoiceSearchButton;
     private EditText mSearchView;
@@ -310,6 +313,7 @@
         mFragmentsFrame = findViewById(R.id.dialtacts_frame);
 
         mRemoveViewContainer = (RemoveView) findViewById(R.id.remove_view_container);
+        mSearchAndRemoveViewContainer = (View) findViewById(R.id.search_and_remove_view_container);
         prepareSearchView();
 
         if (UI.FILTER_CONTACTS_ACTION.equals(intent.getAction())
@@ -564,7 +568,7 @@
     final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
-            mSearchViewContainer.setVisibility(View.GONE);
+            mSearchAndRemoveViewContainer.setVisibility(View.GONE);
         }
     };
 
@@ -583,43 +587,38 @@
     }
 
     public void hideSearchBar() {
-       hideSearchBar(true);
-    }
+        final int height = mSearchAndRemoveViewContainer.getHeight();
+        mSearchAndRemoveViewContainer.animate().cancel();
+        mSearchAndRemoveViewContainer.setAlpha(1);
+        mSearchAndRemoveViewContainer.setTranslationY(0);
+        mSearchAndRemoveViewContainer.animate().withLayer().alpha(0)
+                .translationY(-height).setDuration(200)
+                .setListener(mHideListener);
 
-    public void hideSearchBar(boolean shiftView) {
-        if (shiftView) {
-            mSearchViewContainer.animate().cancel();
-            mSearchViewContainer.setAlpha(1);
-            mSearchViewContainer.setTranslationY(0);
-            mSearchViewContainer.animate().withLayer().alpha(0).translationY(-mSearchView.getHeight())
-                    .setDuration(200).setListener(mHideListener);
-
-            mFragmentsFrame.animate().withLayer()
-                    .translationY(-mSearchViewContainer.getHeight()).setDuration(200).setListener(
-                    new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            mFragmentsFrame.setTranslationY(0);
-                        }
-                    });
-        } else {
-            mSearchViewContainer.setTranslationY(-mSearchView.getHeight());
-        }
+        mFragmentsFrame.animate().withLayer()
+                .translationY(-height).setDuration(200).setListener(
+                new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mFragmentsFrame.setTranslationY(0);
+                    }
+                });
     }
 
     public void showSearchBar() {
-        mSearchViewContainer.animate().cancel();
-        mSearchViewContainer.setAlpha(0);
-        mSearchViewContainer.setTranslationY(-mSearchViewContainer.getHeight());
-        mSearchViewContainer.animate().withLayer().alpha(1).translationY(0).setDuration(200)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        mSearchViewContainer.setVisibility(View.VISIBLE);
-                    }
-                });
+        final int height = mSearchAndRemoveViewContainer.getHeight();
+        mSearchAndRemoveViewContainer.animate().cancel();
+        mSearchAndRemoveViewContainer.setAlpha(0);
+        mSearchAndRemoveViewContainer.setTranslationY(-height);
+        mSearchAndRemoveViewContainer.animate().withLayer().alpha(1).translationY(0)
+                .setDuration(200).setListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationStart(Animator animation) {
+                            mSearchAndRemoveViewContainer.setVisibility(View.VISIBLE);
+                        }
+                    });
 
-        mFragmentsFrame.setTranslationY(-mSearchViewContainer.getHeight());
+        mFragmentsFrame.setTranslationY(-height);
         mFragmentsFrame.animate().withLayer().translationY(0).setDuration(200)
                 .setListener(
                         new AnimatorListenerAdapter() {
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java b/src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java
index 1dbae65..826dec0 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java
@@ -74,7 +74,9 @@
     };
 
     private VoicemailPlaybackPresenter mPresenter;
-    private ScheduledExecutorService mScheduledExecutorService;
+    private static int mMediaPlayerRefCount = 0;
+    private static MediaPlayerProxy mMediaPlayerInstance;
+    private static ScheduledExecutorService mScheduledExecutorService;
     private View mPlaybackLayout;
 
     @Override
@@ -87,7 +89,6 @@
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
-        mScheduledExecutorService = createScheduledExecutorService();
         Bundle arguments = getArguments();
         Preconditions.checkNotNull(arguments, "fragment must be started with arguments");
         Uri voicemailUri = arguments.getParcelable(EXTRA_VOICEMAIL_URI);
@@ -99,8 +100,8 @@
                 powerManager.newWakeLock(
                         PowerManager.SCREEN_DIM_WAKE_LOCK, getClass().getSimpleName());
         mPresenter = new VoicemailPlaybackPresenter(createPlaybackViewImpl(),
-                createMediaPlayer(mScheduledExecutorService), voicemailUri,
-                mScheduledExecutorService, startPlayback,
+                getMediaPlayerInstance(), voicemailUri,
+                getScheduledExecutorServiceInstance(), startPlayback,
                 AsyncTaskExecutors.createAsyncTaskExecutor(), wakeLock);
         mPresenter.onCreate(savedInstanceState);
     }
@@ -113,8 +114,8 @@
 
     @Override
     public void onDestroy() {
+        shutdownMediaPlayer();
         mPresenter.onDestroy();
-        mScheduledExecutorService.shutdown();
         super.onDestroy();
     }
 
@@ -129,12 +130,36 @@
                 mPlaybackLayout);
     }
 
-    private MediaPlayerProxy createMediaPlayer(ExecutorService executorService) {
-        return VariableSpeed.createVariableSpeed(executorService);
+    private static synchronized MediaPlayerProxy getMediaPlayerInstance() {
+        ++mMediaPlayerRefCount;
+        if (mMediaPlayerInstance == null) {
+            mMediaPlayerInstance = VariableSpeed.createVariableSpeed(
+                    getScheduledExecutorServiceInstance());
+        }
+        return mMediaPlayerInstance;
     }
 
-    private ScheduledExecutorService createScheduledExecutorService() {
-        return Executors.newScheduledThreadPool(NUMBER_OF_THREADS_IN_POOL);
+    private static synchronized ScheduledExecutorService getScheduledExecutorServiceInstance() {
+        if (mScheduledExecutorService == null) {
+            mScheduledExecutorService = Executors.newScheduledThreadPool(
+                    NUMBER_OF_THREADS_IN_POOL);
+        }
+        return mScheduledExecutorService;
+    }
+
+    private static synchronized void shutdownMediaPlayer() {
+        --mMediaPlayerRefCount;
+        if (mMediaPlayerRefCount > 0) {
+            return;
+        }
+        if (mScheduledExecutorService != null) {
+            mScheduledExecutorService.shutdown();
+            mScheduledExecutorService = null;
+        }
+        if (mMediaPlayerInstance != null) {
+            mMediaPlayerInstance.release();
+            mMediaPlayerInstance = null;
+        }
     }
 
     /**
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
index ebda0eb..085ef66 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
@@ -35,6 +35,7 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 
+import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
@@ -311,6 +312,7 @@
                             mPlayer.setDataSource(mView.getDataSourceContext(), mVoicemailUri);
                             mPlayer.setAudioStreamType(PLAYBACK_STREAM);
                             mPlayer.prepare();
+                            mDuration.set(mPlayer.getDuration());
                             return null;
                         } catch (Exception e) {
                             return e;
@@ -344,7 +346,7 @@
         mView.setSpeakerPhoneOn(mView.isSpeakerPhoneOn());
         mView.setRateDecreaseButtonListener(createRateDecreaseListener());
         mView.setRateIncreaseButtonListener(createRateIncreaseListener());
-        mView.setClipPosition(0, mPlayer.getDuration());
+        mView.setClipPosition(0, mDuration.get());
         mView.playbackStopped();
         // Always disable on stop.
         mView.disableProximitySensor();
@@ -363,6 +365,10 @@
     }
 
     public void onDestroy() {
+        if (mPrepareTask != null) {
+            mPrepareTask.cancel(false);
+            mPrepareTask = null;
+        }
         mPlayer.release();
         if (mFetchResultHandler != null) {
             mFetchResultHandler.destroy();
@@ -430,49 +436,67 @@
         }
     }
 
+    private class AsyncPrepareTask extends AsyncTask<Void, Void, Exception> {
+        private int mClipPositionInMillis;
+
+        AsyncPrepareTask(int clipPositionInMillis) {
+            mClipPositionInMillis = clipPositionInMillis;
+        }
+
+        @Override
+        public Exception doInBackground(Void... params) {
+            try {
+                if (!mPlayer.isReadyToPlay()) {
+                    mPlayer.reset();
+                    mPlayer.setDataSource(mView.getDataSourceContext(), mVoicemailUri);
+                    mPlayer.setAudioStreamType(PLAYBACK_STREAM);
+                    mPlayer.prepare();
+                }
+                return null;
+            } catch (Exception e) {
+                return e;
+            }
+        }
+
+        @Override
+        public void onPostExecute(Exception exception) {
+            mPrepareTask = null;
+            if (exception == null) {
+                final int duration = mPlayer.getDuration();
+                mDuration.set(duration);
+                int startPosition =
+                    constrain(mClipPositionInMillis, 0, duration);
+                mPlayer.seekTo(startPosition);
+                mView.setClipPosition(startPosition, duration);
+                try {
+                    // Can throw RejectedExecutionException
+                    mPlayer.start();
+                    mView.playbackStarted();
+                    if (!mWakeLock.isHeld()) {
+                        mWakeLock.acquire();
+                    }
+                    // Only enable if we are not currently using the speaker phone.
+                    if (!mView.isSpeakerPhoneOn()) {
+                        mView.enableProximitySensor();
+                    }
+                    // Can throw RejectedExecutionException
+                    mPositionUpdater.startUpdating(startPosition, duration);
+                } catch (RejectedExecutionException e) {
+                    handleError(e);
+                }
+            } else {
+                handleError(exception);
+            }
+        }
+    }
+
     private void resetPrepareStartPlaying(final int clipPositionInMillis) {
         if (mPrepareTask != null) {
             mPrepareTask.cancel(false);
+            mPrepareTask = null;
         }
         mPrepareTask = mAsyncTaskExecutor.submit(Tasks.RESET_PREPARE_START_MEDIA_PLAYER,
-                new AsyncTask<Void, Void, Exception>() {
-                    @Override
-                    public Exception doInBackground(Void... params) {
-                        try {
-                            mPlayer.reset();
-                            mPlayer.setDataSource(mView.getDataSourceContext(), mVoicemailUri);
-                            mPlayer.setAudioStreamType(PLAYBACK_STREAM);
-                            mPlayer.prepare();
-                            return null;
-                        } catch (Exception e) {
-                            return e;
-                        }
-                    }
-
-                    @Override
-                    public void onPostExecute(Exception exception) {
-                        mPrepareTask = null;
-                        if (exception == null) {
-                            mDuration.set(mPlayer.getDuration());
-                            int startPosition =
-                                    constrain(clipPositionInMillis, 0, mDuration.get());
-                            mView.setClipPosition(startPosition, mDuration.get());
-                            mPlayer.seekTo(startPosition);
-                            mPlayer.start();
-                            mView.playbackStarted();
-                            if (!mWakeLock.isHeld()) {
-                                mWakeLock.acquire();
-                            }
-                            // Only enable if we are not currently using the speaker phone.
-                            if (!mView.isSpeakerPhoneOn()) {
-                                mView.enableProximitySensor();
-                            }
-                            mPositionUpdater.startUpdating(startPosition, mDuration.get());
-                        } else {
-                            handleError(exception);
-                        }
-                    }
-                });
+                new AsyncPrepareTask(clipPositionInMillis));
     }
 
     private void handleError(Exception e) {
@@ -598,6 +622,7 @@
             synchronized (mLock) {
                 if (mScheduledFuture != null) {
                     mScheduledFuture.cancel(false);
+                    mScheduledFuture = null;
                 }
                 mScheduledFuture = mExecutorService.scheduleAtFixedRate(this, 0, mPeriodMillis,
                         TimeUnit.MILLISECONDS);
@@ -620,6 +645,7 @@
         }
         if (mPrepareTask != null) {
             mPrepareTask.cancel(false);
+            mPrepareTask = null;
         }
         if (mWakeLock.isHeld()) {
             mWakeLock.release();