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/res/values/strings.xml b/res/values/strings.xml
index 0c9893b..abe1d01 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1635,7 +1635,10 @@
<string name="voicemail_initial_time">00:05</string>
<!-- Message to show when there is an error playing back the voicemail. [CHAR LIMIT=40] -->
- <string name="voicemail_playback_error">Could not play voicemail</string>
+ <string name="voicemail_playback_error">failed to play voicemail</string>
+
+ <!-- Message to display before we have prepared the media player, i.e. before we know duration. [CHAR LIMIT=40] -->
+ <string name="voicemail_buffering">buffering...</string>
<!-- The header in the call log used to identify missed calls and voicemail that have not yet been consumed [CHAR LIMIT=10] -->
<string name="call_log_new_header">New</string>
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);
diff --git a/tests/src/com/android/contacts/CallDetailActivityTest.java b/tests/src/com/android/contacts/CallDetailActivityTest.java
index 7e225c4..33d5cc9 100644
--- a/tests/src/com/android/contacts/CallDetailActivityTest.java
+++ b/tests/src/com/android/contacts/CallDetailActivityTest.java
@@ -16,10 +16,14 @@
package com.android.contacts;
+import com.android.contacts.test.InjectedServices;
+import com.android.contacts.util.BackgroundTaskService;
import com.android.contacts.util.IntegrationTestUtils;
import com.android.contacts.util.LocaleTestUtils;
+import com.android.contacts.util.FakeBackgroundTaskService;
import com.android.internal.view.menu.ContextMenuBuilder;
import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.Executors;
import android.app.Activity;
import android.content.ContentResolver;
@@ -46,6 +50,7 @@
private Uri mUri;
private IntegrationTestUtils mTestUtils;
private LocaleTestUtils mLocaleTestUtils;
+ private FakeBackgroundTaskService mMockBackgroundTaskService;
public CallDetailActivityTest() {
super(CallDetailActivity.class);
@@ -54,6 +59,11 @@
@Override
protected void setUp() throws Exception {
super.setUp();
+ InjectedServices injectedServices = new InjectedServices();
+ mMockBackgroundTaskService = new FakeBackgroundTaskService();
+ injectedServices.setSystemService(BackgroundTaskService.BACKGROUND_TASK_SERVICE,
+ mMockBackgroundTaskService);
+ ContactsApplication.injectServices(injectedServices);
// I don't like the default of focus-mode for tests, the green focus border makes the
// screenshots look weak.
setActivityInitialTouchMode(true);
@@ -70,9 +80,47 @@
mLocaleTestUtils = null;
cleanUpUri();
mTestUtils = null;
+ ContactsApplication.injectServices(null);
super.tearDown();
}
+ public void testInitialActivityStartsWithBuffering() throws Throwable {
+ setActivityIntentForTestVoicemailEntry();
+ CallDetailActivity activity = getActivity();
+ // When the activity first starts, we will show "buffering..." on the screen.
+ // The duration should not be visible.
+ assertHasOneTextViewContaining(activity, "buffering...");
+ assertZeroTextViewsContaining(activity, "00:00");
+ }
+
+ public void testInvalidVoicemailShowsErrorMessage() throws Throwable {
+ setActivityIntentForTestVoicemailEntry();
+ CallDetailActivity activity = getActivity();
+ // If we run all the background tasks, one of them should have prepared the media player.
+ // Preparing the media player will have thrown an IOException since the file doesn't exist.
+ // This should have put a failed to play message on screen, buffering is gone.
+ runAllBackgroundTasks();
+ assertHasOneTextViewContaining(activity, "failed to play voicemail");
+ assertZeroTextViewsContaining(activity, "buffering...");
+ }
+
+ public void testOnResumeDoesNotCreateManyFragments() throws Throwable {
+ // There was a bug where every time the activity was resumed, a new fragment was created.
+ // Before the fix, this was failing reproducibly with at least 3 "buffering..." views.
+ setActivityIntentForTestVoicemailEntry();
+ final CallDetailActivity activity = getActivity();
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ getInstrumentation().callActivityOnPause(activity);
+ getInstrumentation().callActivityOnResume(activity);
+ getInstrumentation().callActivityOnPause(activity);
+ getInstrumentation().callActivityOnResume(activity);
+ }
+ });
+ assertHasOneTextViewContaining(activity, "buffering...");
+ }
+
/**
* Test for bug where increase rate button with invalid voicemail causes a crash.
* <p>
@@ -184,4 +232,24 @@
private ContentResolver getContentResolver() {
return getInstrumentation().getTargetContext().getContentResolver();
}
+
+ private TextView assertHasOneTextViewContaining(Activity activity, String text)
+ throws Throwable {
+ List<TextView> views = mTestUtils.getTextViewsWithString(activity, text);
+ assertEquals("There should have been one TextView with text '" + text + "' but found "
+ + views, 1, views.size());
+ return views.get(0);
+ }
+
+ private void assertZeroTextViewsContaining(Activity activity, String text) throws Throwable {
+ List<TextView> views = mTestUtils.getTextViewsWithString(activity, text);
+ assertEquals("There should have been no TextViews with text '" + text + "' but found "
+ + views, 0, views.size());
+ }
+
+ private void runAllBackgroundTasks() {
+ mMockBackgroundTaskService.runAllBackgroundTasks(
+ Executors.sameThreadExecutor(),
+ FakeBackgroundTaskService.createMainSyncExecutor(getInstrumentation()));
+ }
}
diff --git a/tests/src/com/android/contacts/activities/PeopleActivityTest.java b/tests/src/com/android/contacts/activities/PeopleActivityTest.java
index 8cef001..fe6d60a 100644
--- a/tests/src/com/android/contacts/activities/PeopleActivityTest.java
+++ b/tests/src/com/android/contacts/activities/PeopleActivityTest.java
@@ -102,6 +102,12 @@
ContactsApplication.injectServices(services);
}
+ @Override
+ protected void tearDown() throws Exception {
+ ContactsApplication.injectServices(null);
+ super.tearDown();
+ }
+
public void testSingleAccountNoGroups() {
// This two-pane UI test only makes sense if we run with two panes.
// Let's ignore this in the single pane case
diff --git a/tests/src/com/android/contacts/util/FakeBackgroundTaskService.java b/tests/src/com/android/contacts/util/FakeBackgroundTaskService.java
new file mode 100644
index 0000000..a9d80b4
--- /dev/null
+++ b/tests/src/com/android/contacts/util/FakeBackgroundTaskService.java
@@ -0,0 +1,81 @@
+/*
+ * 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.google.common.collect.Lists;
+import com.google.common.util.concurrent.Executors;
+
+import android.app.Instrumentation;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Simple test implementation of BackgroundTaskService.
+ */
+public class FakeBackgroundTaskService extends BackgroundTaskService {
+ private final List<BackgroundTask> mSubmittedTasks = Lists.newArrayList();
+
+ @Override
+ public void submit(BackgroundTask task) {
+ mSubmittedTasks.add(task);
+ }
+
+ public List<BackgroundTask> getSubmittedTasks() {
+ return mSubmittedTasks;
+ }
+
+ public static Executor createMainSyncExecutor(final Instrumentation instrumentation) {
+ return new Executor() {
+ @Override
+ public void execute(Runnable runnable) {
+ instrumentation.runOnMainSync(runnable);
+ }
+ };
+ }
+
+ /**
+ * Executes the background tasks, using the supplied executors.
+ * <p>
+ * This is most commonly used with {@link Executors#sameThreadExecutor()} for the first argument
+ * and {@link #createMainSyncExecutor(Instrumentation)}, so that the test thread can directly
+ * run the tasks in the background, then have the onPostExecute methods happen on the main ui
+ * thread.
+ */
+ public void runAllBackgroundTasks(Executor doInBackgroundExecutor,
+ final Executor onPostExecuteExecutor) {
+ for (final BackgroundTask task : getSubmittedTasks()) {
+ final Object visibilityLock = new Object();
+ doInBackgroundExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (visibilityLock) {
+ task.doInBackground();
+ }
+ onPostExecuteExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (visibilityLock) {
+ task.onPostExecute();
+ }
+ }
+ });
+ }
+ });
+ }
+ }
+}