Merge "Import revised translations."
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index 13d2e6c..79e9ae5 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -287,7 +287,16 @@
mCallerIdThread.start();
}
+ /**
+ * Stops the background thread that processes updates and cancels any pending requests to
+ * start it.
+ * <p>
+ * Should be called from the main thread to prevent a race condition between the request to
+ * start the thread being processed and stopping the thread.
+ */
public void stopRequestProcessing() {
+ // Remove any pending requests to start the processing thread.
+ mHandler.removeMessages(START_THREAD);
mDone = true;
if (mCallerIdThread != null) mCallerIdThread.interrupt();
}
diff --git a/src/com/android/contacts/interactions/ContactDeletionInteraction.java b/src/com/android/contacts/interactions/ContactDeletionInteraction.java
index f195c2f..dfcd7b6 100644
--- a/src/com/android/contacts/interactions/ContactDeletionInteraction.java
+++ b/src/com/android/contacts/interactions/ContactDeletionInteraction.java
@@ -21,11 +21,13 @@
import com.android.contacts.model.AccountType;
import com.android.contacts.model.AccountTypeManager;
import com.google.android.collect.Sets;
+import com.google.common.annotations.VisibleForTesting;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.app.FragmentManager;
+import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Context;
import android.content.CursorLoader;
@@ -37,6 +39,7 @@
import android.os.Bundle;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Contacts.Entity;
+import android.util.Log;
import java.util.HashSet;
@@ -69,14 +72,43 @@
private Uri mContactUri;
private boolean mFinishActivityWhenDone;
private Context mContext;
-
private AlertDialog mDialog;
- // Visible for testing
+ /** This is a wrapper around the fragment's loader manager to be used only during testing. */
+ private TestLoaderManager mTestLoaderManager;
+
+ @VisibleForTesting
int mMessageId;
+ /**
+ * Starts the interaction.
+ *
+ * @param activity the activity within which to start the interaction
+ * @param contactUri the URI of the contact to delete
+ * @param finishActivityWhenDone whether to finish the activity upon completion of the
+ * interaction
+ * @return the newly created interaction
+ */
public static ContactDeletionInteraction start(
Activity activity, Uri contactUri, boolean finishActivityWhenDone) {
+ return startWithTestLoaderManager(activity, contactUri, finishActivityWhenDone, null);
+ }
+
+ /**
+ * Starts the interaction and optionally set up a {@link TestLoaderManager}.
+ *
+ * @param activity the activity within which to start the interaction
+ * @param contactUri the URI of the contact to delete
+ * @param finishActivityWhenDone whether to finish the activity upon completion of the
+ * interaction
+ * @param testLoaderManager the {@link TestLoaderManager} to use to load the data, may be null
+ * in which case the default {@link LoaderManager} is used
+ * @return the newly created interaction
+ */
+ @VisibleForTesting
+ static ContactDeletionInteraction startWithTestLoaderManager(
+ Activity activity, Uri contactUri, boolean finishActivityWhenDone,
+ TestLoaderManager testLoaderManager) {
if (contactUri == null) {
return null;
}
@@ -86,10 +118,12 @@
(ContactDeletionInteraction) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
if (fragment == null) {
fragment = new ContactDeletionInteraction();
+ fragment.setTestLoaderManager(testLoaderManager);
fragment.setContactUri(contactUri);
fragment.setFinishActivityWhenDone(finishActivityWhenDone);
fragmentManager.beginTransaction().add(fragment, FRAGMENT_TAG).commit();
} else {
+ fragment.setTestLoaderManager(testLoaderManager);
fragment.setContactUri(contactUri);
fragment.setFinishActivityWhenDone(finishActivityWhenDone);
}
@@ -97,6 +131,24 @@
}
@Override
+ public LoaderManager getLoaderManager() {
+ // Return the TestLoaderManager if one is set up.
+ LoaderManager loaderManager = super.getLoaderManager();
+ if (mTestLoaderManager != null) {
+ // Set the delegate: this operation is idempotent, so let's just do it every time.
+ mTestLoaderManager.setDelegate(loaderManager);
+ return mTestLoaderManager;
+ } else {
+ return loaderManager;
+ }
+ }
+
+ /** Sets the TestLoaderManager that is used to wrap the actual LoaderManager in tests. */
+ private void setTestLoaderManager(TestLoaderManager mockLoaderManager) {
+ mTestLoaderManager = mockLoaderManager;
+ }
+
+ @Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mContext = activity;
diff --git a/src/com/android/contacts/interactions/TestLoaderManager.java b/src/com/android/contacts/interactions/TestLoaderManager.java
new file mode 100644
index 0000000..db975ed
--- /dev/null
+++ b/src/com/android/contacts/interactions/TestLoaderManager.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2010 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.interactions;
+
+import android.app.Activity;
+import android.app.LoaderManager;
+import android.content.AsyncTaskLoader;
+import android.content.Loader;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import junit.framework.Assert;
+
+/**
+ * A {@link LoaderManager} that records which loaders have been completed.
+ * <p>
+ * You should wrap the existing LoaderManager with an instance of this class, which will then
+ * delegate to the original object.
+ * <p>
+ * Typically, one would override {@link Activity#getLoaderManager()} to return the
+ * TestLoaderManager and ensuring it wraps the {@link LoaderManager} for this object, e.g.:
+ * <pre>
+ * private TestLoaderManager mTestLoaderManager;
+ *
+ * public LoaderManager getLoaderManager() {
+ * LoaderManager loaderManager = super.getLoaderManager();
+ * if (mTestLoaderManager != null) {
+ * mTestLoaderManager.setDelegate(loaderManager);
+ * return mTestLoaderManager;
+ * } else {
+ * return loaderManager;
+ * }
+ * }
+ *
+ * void setTestLoaderManager(TestLoaderManager testLoaderManager) {
+ * mTestLoaderManager = testLoaderManager;
+ * }
+ * </pre>
+ * In the tests, one would set the TestLoaderManager upon creating the activity, and then wait for
+ * the loader to complete.
+ * <pre>
+ * public void testLoadedCorrect() {
+ * TestLoaderManager testLoaderManager = new TestLoaderManager();
+ * getActivity().setTestLoaderManager(testLoaderManager);
+ * runOnUiThread(new Runnable() { public void run() { getActivity().startLoading(); } });
+ * testLoaderManager.waitForLoader(R.id.test_loader_id);
+ * }
+ * </pre>
+ * If the loader completes before the call to {@link #waitForLoaders(int...)}, the TestLoaderManager
+ * will have stored the fact that the loader has completed and correctly terminate immediately.
+ * <p>
+ * It one needs to wait for the same loader multiple times, call {@link #reset()} between the them
+ * as in:
+ * <pre>
+ * public void testLoadedCorrect() {
+ * TestLoaderManager testLoaderManager = new TestLoaderManager();
+ * getActivity().setTestLoaderManager(testLoaderManager);
+ * runOnUiThread(new Runnable() { public void run() { getActivity().startLoading(); } });
+ * testLoaderManager.waitForLoader(R.id.test_loader_id);
+ * testLoaderManager.reset();
+ * // Load and wait again.
+ * runOnUiThread(new Runnable() { public void run() { getActivity().startLoading(); } });
+ * testLoaderManager.waitForLoader(R.id.test_loader_id);
+ * }
+ * </pre>
+ */
+public class TestLoaderManager extends LoaderManager {
+ private static final String TAG = "TestLoaderManager";
+
+ private final HashSet<Integer> mFinishedLoaders;
+
+ private LoaderManager mDelegate;
+
+ public TestLoaderManager() {
+ mFinishedLoaders = new HashSet<Integer>();
+ }
+
+ /**
+ * Sets the object to which we delegate the actual work.
+ * <p>
+ * It can not be set to null. Once set, it cannot be changed (but it allows setting it to the
+ * same value again).
+ */
+ public void setDelegate(LoaderManager delegate) {
+ if (delegate == null || (mDelegate != null && mDelegate != delegate)) {
+ throw new IllegalArgumentException("TestLoaderManager cannot be shared");
+ }
+
+ mDelegate = delegate;
+ }
+
+ public LoaderManager getDelegate() {
+ return mDelegate;
+ }
+
+ public void reset() {
+ mFinishedLoaders.clear();
+ }
+
+ /**
+ * Waits for the specified loaders to complete loading.
+ * <p>
+ * If one of the loaders has already completed since the last call to {@link #reset()}, it will
+ * not wait for it to complete again.
+ */
+ public synchronized void waitForLoaders(int... loaderIds) {
+ List<Loader<?>> loaders = new ArrayList<Loader<?>>(loaderIds.length);
+ for (int loaderId : loaderIds) {
+ if (mFinishedLoaders.contains(loaderId)) {
+ // This loader has already completed since the last reset, do not wait for it.
+ continue;
+ }
+
+ final AsyncTaskLoader<?> loader =
+ (AsyncTaskLoader<?>) mDelegate.getLoader(loaderId);
+ if (loader == null) {
+ Assert.fail("Loader does not exist: " + loaderId);
+ return;
+ }
+
+ loaders.add(loader);
+ }
+
+ waitForLoaders(loaders.toArray(new Loader<?>[0]));
+ }
+
+ /**
+ * Waits for the specified loaders to complete loading.
+ */
+ public static void waitForLoaders(Loader<?>... loaders) {
+ // We want to wait for each loader using a separate thread, so that we can
+ // simulate race conditions.
+ Thread[] waitThreads = new Thread[loaders.length];
+ for (int i = 0; i < loaders.length; i++) {
+ final AsyncTaskLoader<?> loader = (AsyncTaskLoader<?>) loaders[i];
+ waitThreads[i] = new Thread("LoaderWaitingThread" + i) {
+ @Override
+ public void run() {
+ try {
+ loader.waitForLoader();
+ } catch (Throwable e) {
+ Log.e(TAG, "Exception while waiting for loader: " + loader.getId(), e);
+ Assert.fail("Exception while waiting for loader: " + loader.getId());
+ }
+ }
+ };
+ waitThreads[i].start();
+ }
+
+ // Now we wait for all these threads to finish
+ for (Thread thread : waitThreads) {
+ try {
+ thread.join();
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+ }
+
+ @Override
+ public <D> Loader<D> initLoader(final int id, Bundle args, final LoaderCallbacks<D> callback) {
+ return mDelegate.initLoader(id, args, new LoaderManager.LoaderCallbacks<D>() {
+ @Override
+ public Loader<D> onCreateLoader(int id, Bundle args) {
+ return callback.onCreateLoader(id, args);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<D> loader, D data) {
+ callback.onLoadFinished(loader, data);
+ synchronized (this) {
+ mFinishedLoaders.add(id);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<D> loader) {
+ callback.onLoaderReset(loader);
+ }
+ });
+ }
+
+ @Override
+ public <D> Loader<D> restartLoader(int id, Bundle args, LoaderCallbacks<D> callback) {
+ return mDelegate.restartLoader(id, args, callback);
+ }
+
+ @Override
+ public void destroyLoader(int id) {
+ mDelegate.destroyLoader(id);
+ }
+
+ @Override
+ public <D> Loader<D> getLoader(int id) {
+ return mDelegate.getLoader(id);
+ }
+
+ @Override
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ mDelegate.dump(prefix, fd, writer, args);
+ }
+}
diff --git a/tests/src/com/android/contacts/activities/PeopleActivityTest.java b/tests/src/com/android/contacts/activities/PeopleActivityTest.java
index 4c1bb16..43158e5 100644
--- a/tests/src/com/android/contacts/activities/PeopleActivityTest.java
+++ b/tests/src/com/android/contacts/activities/PeopleActivityTest.java
@@ -20,6 +20,7 @@
import com.android.contacts.ContactsApplication;
import com.android.contacts.R;
import com.android.contacts.detail.ContactDetailFragment;
+import com.android.contacts.interactions.TestLoaderManager;
import com.android.contacts.list.ContactBrowseListFragment;
import com.android.contacts.model.AccountType;
import com.android.contacts.model.AccountTypeManager;
@@ -129,7 +130,7 @@
// TODO: wait for detail loader
// TODO: wait for lookup key loading
- mContext.waitForLoaders(filterLoader, listLoader);
+ TestLoaderManager.waitForLoaders(filterLoader, listLoader);
getInstrumentation().waitForIdleSync();
diff --git a/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java b/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
index 1db0f4c..c1eefc0 100644
--- a/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
+++ b/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
@@ -129,16 +129,18 @@
private void assertWithMessageId(int messageId) {
final FragmentTestActivity activity = getActivity();
+ final TestLoaderManager mockLoaderManager = new TestLoaderManager();
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
- mFragment = ContactDeletionInteraction.start(activity, CONTACT_URI, false);
+ mFragment = ContactDeletionInteraction.startWithTestLoaderManager(
+ activity, CONTACT_URI, false, mockLoaderManager);
}
});
getInstrumentation().waitForIdleSync();
- mContext.waitForLoaders(mFragment.getLoaderManager(), R.id.dialog_delete_contact_loader_id);
+ mockLoaderManager.waitForLoaders(R.id.dialog_delete_contact_loader_id);
getInstrumentation().waitForIdleSync();
diff --git a/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java b/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
index 9af1350..cea22b2 100644
--- a/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
+++ b/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
@@ -16,22 +16,14 @@
package com.android.contacts.tests.mocks;
-//import com.android.providers.contacts.ContactsMockPackageManager;
-
-import android.app.LoaderManager;
-import android.content.AsyncTaskLoader;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
-import android.content.Loader;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
-import android.util.Log;
-
-import junit.framework.Assert;
/**
* A mock context for contacts unit tests. Forwards everything to
@@ -86,58 +78,4 @@
mContactsProvider.verify();
mSettingsProvider.verify();
}
-
- /**
- * Waits for the specified loaders to complete loading.
- */
- public void waitForLoaders(LoaderManager loaderManager, int... loaderIds) {
- Loader<?>[] loaders = new Loader<?>[loaderIds.length];
- for (int i = 0; i < loaderIds.length; i++) {
- final int loaderId = loaderIds[i];
- final AsyncTaskLoader<?> loader =
- (AsyncTaskLoader<?>) loaderManager.getLoader(loaderId);
- if (loader == null) {
- Assert.fail("Loader does not exist: " + loaderId);
- return;
- }
-
- loaders[i] = loader;
- }
-
- waitForLoaders(loaders);
- }
-
- /**
- * Waits for the specified loaders to complete loading.
- */
- public void waitForLoaders(Loader<?>... loaders) {
- // We want to wait for each loader using a separate thread, so that we can
- // simulate race conditions.
- Thread[] waitThreads = new Thread[loaders.length];
- for (int i = 0; i < loaders.length; i++) {
- final AsyncTaskLoader<?> loader = (AsyncTaskLoader<?>) loaders[i];
- waitThreads[i] = new Thread("LoaderWaitingThread" + i) {
- @Override
- public void run() {
- try {
- loader.waitForLoader();
- } catch (Throwable e) {
- Log.e(TAG, "Exception while waiting for loader: " + loader.getId(), e);
- Assert.fail("Exception while waiting for loader: " + loader.getId());
- }
- }
- };
- waitThreads[i].start();
- }
-
- // Now we wait for all these threads to finish
- for (Thread thread : waitThreads) {
- try {
- thread.join();
- } catch (InterruptedException e) {
- // Ignore
- }
- }
- }
-
}