Prepare playback immediately, new BackgroundTaskService.

- Immediately we start the CallDetailActivity with a voicemail, do the
  preparing of the media so we can see the duration.
- Use the duration text field to show "buffering..." until media source
  is prepared.
- Do the preparing in the background, this fixes a strict mode
  violation.
- If there's an error preparing, show an error message in that field.
- Add tests for both of the above cases.

BackgroundTaskService:
- Introduces new BackgroundTaskService, lightweight method for
  submitting AsyncTask objects.
- Introduces BackgroundTask interface and simple AbstractBackgroundTask
  abstract implementation.
- Adds BackgroundTaskService to ContactsApplication allowing Activity
  objects to get hold of a regular background task service.
- Adds a MockBackgroundTaskService for use with injecting for test, so
  that we can prevent or control processing of background tasks.

Other:
- Every time we resume the Activity, we were causing a new voicemail
  fragment to be created.  There was no bug tracking this, I just
  noticed it because of occasionally flaky tests.  Added a fix and test
  to catch it again next time.
- Fixes missing tear down method in PeopleActivityTest.

Bug: 5115133
Bug: 5059965
Bug: 5114261
Bug: 5113695
Change-Id: Ia2469229fa756da8b3977231fbf23a9d3fb379ce
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index 163069a..43e6fc7 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -19,6 +19,9 @@
 import com.android.contacts.calllog.CallDetailHistoryAdapter;
 import com.android.contacts.calllog.CallTypeHelper;
 import com.android.contacts.calllog.PhoneNumberHelper;
+import com.android.contacts.util.AbstractBackgroundTask;
+import com.android.contacts.util.BackgroundTask;
+import com.android.contacts.util.BackgroundTaskService;
 import com.android.contacts.voicemail.VoicemailPlaybackFragment;
 import com.android.contacts.voicemail.VoicemailStatusHelper;
 import com.android.contacts.voicemail.VoicemailStatusHelper.StatusMessage;
@@ -34,7 +37,6 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Bundle;
 import android.provider.CallLog;
 import android.provider.CallLog.Calls;
@@ -87,6 +89,7 @@
     private ImageView mMainActionView;
     private ImageButton mMainActionPushLayerView;
     private ImageView mContactBackgroundView;
+    private BackgroundTaskService mBackgroundTaskService;
 
     private String mNumber = null;
     private String mDefaultCountryIso;
@@ -144,6 +147,8 @@
 
         setContentView(R.layout.call_detail);
 
+        mBackgroundTaskService = (BackgroundTaskService) getApplicationContext().getSystemService(
+                BackgroundTaskService.BACKGROUND_TASK_SERVICE);
         mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
         mResources = getResources();
 
@@ -164,13 +169,13 @@
         mContactPhotoManager = ContactPhotoManager.getInstance(this);
         getListView().setOnItemClickListener(this);
         configureActionBar();
+        optionallyHandleVoicemail();
     }
 
     @Override
     public void onResume() {
         super.onResume();
         updateData(getCallLogEntryUris());
-        optionallyHandleVoicemail();
     }
 
     /**
@@ -210,15 +215,14 @@
     }
 
     private void markVoicemailAsRead(final Uri voicemailUri) {
-        new AsyncTask<Void, Void, Void>() {
+        mBackgroundTaskService.submit(new AbstractBackgroundTask() {
             @Override
-            protected Void doInBackground(Void... params) {
+            public void doInBackground() {
                 ContentValues values = new ContentValues();
                 values.put(Voicemails.IS_READ, true);
                 getContentResolver().update(voicemailUri, values, null, null);
-                return null;
             }
-        }.execute();
+        });
     }
 
     /**
@@ -662,12 +666,16 @@
             }
             callIds.append(ContentUris.parseId(callUri));
         }
-        runInBackgroundThenFinishActivity(new Runnable() {
+        mBackgroundTaskService.submit(new BackgroundTask() {
             @Override
-            public void run() {
+            public void doInBackground() {
                 getContentResolver().delete(Calls.CONTENT_URI_WITH_VOICEMAIL,
                         Calls._ID + " IN (" + callIds + ")", null);
             }
+            @Override
+            public void onPostExecute() {
+                finish();
+            }
         });
     }
     public void onMenuEditNumberBeforeCall(MenuItem menuItem) {
@@ -680,29 +688,16 @@
 
     public void onMenuTrashVoicemail(MenuItem menuItem) {
         final Uri voicemailUri = getVoicemailUri();
-        runInBackgroundThenFinishActivity(new Runnable() {
+        mBackgroundTaskService.submit(new BackgroundTask() {
             @Override
-            public void run() {
+            public void doInBackground() {
                 getContentResolver().delete(voicemailUri, null, null);
             }
-        });
-    }
-
-    /**
-     * Run a task in the background, and then finish this activity when the task is done.
-     */
-    private void runInBackgroundThenFinishActivity(final Runnable runnable) {
-        new AsyncTask<Void, Void, Void>() {
             @Override
-            protected Void doInBackground(Void... params) {
-                runnable.run();
-                return null;
-            }
-            @Override
-            protected void onPostExecute(Void result) {
+            public void onPostExecute() {
                 finish();
             }
-        }.execute();
+        });
     }
 
     private void configureActionBar() {
diff --git a/src/com/android/contacts/ContactsApplication.java b/src/com/android/contacts/ContactsApplication.java
index 1c8c080..1e791b6 100644
--- a/src/com/android/contacts/ContactsApplication.java
+++ b/src/com/android/contacts/ContactsApplication.java
@@ -18,6 +18,7 @@
 
 import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.test.InjectedServices;
+import com.android.contacts.util.BackgroundTaskService;
 import com.google.common.annotations.VisibleForTesting;
 
 import android.app.Application;
@@ -33,6 +34,7 @@
     private static InjectedServices sInjectedServices;
     private AccountTypeManager mAccountTypeManager;
     private ContactPhotoManager mContactPhotoManager;
+    private BackgroundTaskService mBackgroundTaskService;
 
     /**
      * Overrides the system services with mocks for testing.
@@ -93,6 +95,13 @@
             return mContactPhotoManager;
         }
 
+        if (BackgroundTaskService.BACKGROUND_TASK_SERVICE.equals(name)) {
+            if (mBackgroundTaskService == null) {
+                mBackgroundTaskService = BackgroundTaskService.createBackgroundTaskService();
+            }
+            return mBackgroundTaskService;
+        }
+
         return super.getSystemService(name);
     }
 
diff --git a/src/com/android/contacts/util/AbstractBackgroundTask.java b/src/com/android/contacts/util/AbstractBackgroundTask.java
new file mode 100644
index 0000000..c492e7c
--- /dev/null
+++ b/src/com/android/contacts/util/AbstractBackgroundTask.java
@@ -0,0 +1,29 @@
+/*
+ * 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.contacts.util;
+
+import com.android.contacts.util.BackgroundTask;
+
+/**
+ * Base class you can use if you only want to override the {@link #doInBackground()} method.
+ */
+public abstract class AbstractBackgroundTask implements BackgroundTask {
+    @Override
+    public void onPostExecute() {
+        // No action necessary.
+    }
+}
diff --git a/src/com/android/contacts/util/BackgroundTask.java b/src/com/android/contacts/util/BackgroundTask.java
new file mode 100644
index 0000000..ba791fb
--- /dev/null
+++ b/src/com/android/contacts/util/BackgroundTask.java
@@ -0,0 +1,31 @@
+/*
+ * 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.contacts.util;
+
+/**
+ * Simple interface to improve the testability of code using AsyncTasks.
+ * <p>
+ * Provides a trivial replacement for no-arg versions of AsyncTask clients.  We may extend this
+ * to add more functionality as we require.
+ * <p>
+ * The same memory-visibility guarantees are made here as are made for AsyncTask objects, namely
+ * that fields set in {@link #doInBackground()} are visible to {@link #onPostExecute()}.
+ */
+public interface BackgroundTask {
+    public void doInBackground();
+    public void onPostExecute();
+}
diff --git a/src/com/android/contacts/util/BackgroundTaskService.java b/src/com/android/contacts/util/BackgroundTaskService.java
new file mode 100644
index 0000000..00f9e3e
--- /dev/null
+++ b/src/com/android/contacts/util/BackgroundTaskService.java
@@ -0,0 +1,54 @@
+/*
+ * 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.contacts.util;
+
+import android.os.AsyncTask;
+
+/**
+ * Service used to submit tasks to run in the background.
+ * <p>
+ * BackgroundTaskService makes the same memory-visibility guarantees that AsyncTask which it
+ * emulates makes, namely that fields set in the {@link BackgroundTask#doInBackground()} method
+ * will be visible to the {@link BackgroundTask#onPostExecute()} method.
+ */
+public abstract class BackgroundTaskService {
+    public static final String BACKGROUND_TASK_SERVICE = BackgroundTaskService.class.getName();
+
+    public abstract void submit(BackgroundTask task);
+
+    public static BackgroundTaskService createBackgroundTaskService() {
+        return new AsyncTaskBackgroundTaskService();
+    }
+
+    private static final class AsyncTaskBackgroundTaskService extends BackgroundTaskService {
+        @Override
+        public void submit(final BackgroundTask task) {
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... params) {
+                    task.doInBackground();
+                    return null;
+                }
+
+                @Override
+                protected void onPostExecute(Void result) {
+                    task.onPostExecute();
+                }
+            }.execute();
+        }
+    }
+}
diff --git a/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java b/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java
index fcf99b6..0b30cd9 100644
--- a/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java
+++ b/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java
@@ -20,6 +20,7 @@
 import static com.android.contacts.CallDetailActivity.EXTRA_VOICEMAIL_URI;
 
 import com.android.contacts.R;
+import com.android.contacts.util.BackgroundTaskService;
 import com.android.ex.variablespeed.MediaPlayerProxy;
 import com.android.ex.variablespeed.VariableSpeed;
 import com.google.common.base.Preconditions;
@@ -36,7 +37,6 @@
 import android.widget.ImageButton;
 import android.widget.SeekBar;
 import android.widget.TextView;
-import android.widget.Toast;
 
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -97,10 +97,15 @@
         boolean startPlayback = arguments.getBoolean(EXTRA_VOICEMAIL_START_PLAYBACK, false);
         mPresenter = new VoicemailPlaybackPresenter(new PlaybackViewImpl(),
                 createMediaPlayer(mScheduledExecutorService), voicemailUri,
-                mScheduledExecutorService, startPlayback);
+                mScheduledExecutorService, startPlayback, getBackgroundTaskService());
         mPresenter.onCreate(savedInstanceState);
     }
 
+    private BackgroundTaskService getBackgroundTaskService() {
+        return (BackgroundTaskService) getActivity().getApplicationContext().getSystemService(
+                BackgroundTaskService.BACKGROUND_TASK_SERVICE);
+    }
+
     @Override
     public void onSaveInstanceState(Bundle outState) {
         mPresenter.onSaveInstanceState(outState);
@@ -213,6 +218,11 @@
         }
 
         @Override
+        public void setIsBuffering() {
+          mTextController.setPermanentText(getString(R.string.voicemail_buffering));
+        }
+
+        @Override
         public int getDesiredClipPosition() {
             return mPlaybackSeek.getProgress();
         }
@@ -224,7 +234,7 @@
             mStartStopButton.setEnabled(false);
             mPlaybackSeek.setProgress(0);
             mPlaybackSeek.setEnabled(false);
-            Toast.makeText(getActivity(), R.string.voicemail_playback_error, Toast.LENGTH_SHORT);
+            mTextController.setPermanentText(getString(R.string.voicemail_playback_error));
             Log.e(TAG, "Could not play voicemail", e);
         }
 
diff --git a/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
index 6f3a625..e901fab 100644
--- a/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
+++ b/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
@@ -19,6 +19,8 @@
 import static android.util.MathUtils.constrain;
 
 import com.android.contacts.R;
+import com.android.contacts.util.BackgroundTask;
+import com.android.contacts.util.BackgroundTaskService;
 import com.android.ex.variablespeed.MediaPlayerProxy;
 import com.android.ex.variablespeed.SingleThreadedMediaPlayerProxy;
 
@@ -58,6 +60,7 @@
         void setStartStopListener(View.OnClickListener listener);
         void setPositionSeekListener(SeekBar.OnSeekBarChangeListener listener);
         void setSpeakerphoneListener(View.OnClickListener listener);
+        void setIsBuffering();
         void setClipPosition(int clipPositionInMillis, int clipLengthInMillis);
         int getDesiredClipPosition();
         void playbackStarted();
@@ -124,18 +127,48 @@
     private final Uri mVoicemailUri;
     /** Start playing in onCreate iff this is true. */
     private final boolean mStartPlayingImmediately;
+    /** Used to run background tasks that need to interact with the ui. */
+    private final BackgroundTaskService mBackgroundTaskService;
 
     public VoicemailPlaybackPresenter(PlaybackView view, MediaPlayerProxy player,
             Uri voicemailUri, ScheduledExecutorService executorService,
-            boolean startPlayingImmediately) {
+            boolean startPlayingImmediately, BackgroundTaskService backgroundTaskService) {
         mView = view;
         mPlayer = player;
         mVoicemailUri = voicemailUri;
         mStartPlayingImmediately = startPlayingImmediately;
+        mBackgroundTaskService = backgroundTaskService;
         mPositionUpdater = new PositionUpdater(executorService, SLIDER_UPDATE_PERIOD_MILLIS);
     }
 
     public void onCreate(Bundle bundle) {
+        mView.setIsBuffering();
+        mBackgroundTaskService.submit(new BackgroundTask() {
+            private Exception mException;
+
+            @Override
+            public void doInBackground() {
+                try {
+                    mPlayer.reset();
+                    mPlayer.setDataSource(mView.getDataSourceContext(), mVoicemailUri);
+                    mPlayer.prepare();
+                } catch (IOException e) {
+                    mException = e;
+                }
+            }
+
+            @Override
+            public void onPostExecute() {
+                if (mException == null) {
+                    postSuccessfulPrepareActions();
+                } else {
+                    mView.playbackError(mException);
+                }
+            }
+        });
+    }
+
+    private void postSuccessfulPrepareActions() {
         mView.setPositionSeekListener(new PlaybackPositionListener());
         mView.setStartStopListener(new StartStopButtonListener());
         mView.setSpeakerphoneListener(new SpeakerphoneListener());
@@ -144,7 +177,7 @@
         mView.setSpeakerPhoneOn(mView.isSpeakerPhoneOn());
         mView.setRateDecreaseButtonListener(createRateDecreaseListener());
         mView.setRateIncreaseButtonListener(createRateIncreaseListener());
-        mView.setClipPosition(0, 0);
+        mView.setClipPosition(0, mPlayer.getDuration());
         mView.playbackStopped();
         if (mStartPlayingImmediately) {
             resetPrepareStartPlaying(0);