Merge "Make update items/photos highlight when they're clickable"
diff --git a/res/drawable-hdpi/change_photo_box_normal_holo_light.9.png b/res/drawable-hdpi/change_photo_box_normal_holo_light.9.png
new file mode 100644
index 0000000..f026cc8
--- /dev/null
+++ b/res/drawable-hdpi/change_photo_box_normal_holo_light.9.png
Binary files differ
diff --git a/res/drawable-hdpi/change_photo_box_pressed_holo_light.9.png b/res/drawable-hdpi/change_photo_box_pressed_holo_light.9.png
new file mode 100644
index 0000000..a0770ea
--- /dev/null
+++ b/res/drawable-hdpi/change_photo_box_pressed_holo_light.9.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_expander_maximized_holo_light.png b/res/drawable-hdpi/ic_menu_expander_maximized_holo_light.png
index 3c47e9e..41c6eda 100644
--- a/res/drawable-hdpi/ic_menu_expander_maximized_holo_light.png
+++ b/res/drawable-hdpi/ic_menu_expander_maximized_holo_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_expander_minimized_holo_light.png b/res/drawable-hdpi/ic_menu_expander_minimized_holo_light.png
index 6b8c6b0..a3b22bc 100644
--- a/res/drawable-hdpi/ic_menu_expander_minimized_holo_light.png
+++ b/res/drawable-hdpi/ic_menu_expander_minimized_holo_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_remove_field_holo_light.png b/res/drawable-hdpi/ic_menu_remove_field_holo_light.png
index d8172ee..03fd2fb 100644
--- a/res/drawable-hdpi/ic_menu_remove_field_holo_light.png
+++ b/res/drawable-hdpi/ic_menu_remove_field_holo_light.png
Binary files differ
diff --git a/res/drawable-mdpi/change_photo_box_normal_holo_light.9.png b/res/drawable-mdpi/change_photo_box_normal_holo_light.9.png
new file mode 100644
index 0000000..591e6d5
--- /dev/null
+++ b/res/drawable-mdpi/change_photo_box_normal_holo_light.9.png
Binary files differ
diff --git a/res/drawable-mdpi/change_photo_box_pressed_holo_light.9.png b/res/drawable-mdpi/change_photo_box_pressed_holo_light.9.png
new file mode 100644
index 0000000..5d5eee2
--- /dev/null
+++ b/res/drawable-mdpi/change_photo_box_pressed_holo_light.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_expander_maximized_holo_light.png b/res/drawable-mdpi/ic_menu_expander_maximized_holo_light.png
index 3dee74d..342867c 100644
--- a/res/drawable-mdpi/ic_menu_expander_maximized_holo_light.png
+++ b/res/drawable-mdpi/ic_menu_expander_maximized_holo_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_expander_minimized_holo_light.png b/res/drawable-mdpi/ic_menu_expander_minimized_holo_light.png
index 6dba137..f447069 100644
--- a/res/drawable-mdpi/ic_menu_expander_minimized_holo_light.png
+++ b/res/drawable-mdpi/ic_menu_expander_minimized_holo_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_remove_field_holo_light.png b/res/drawable-mdpi/ic_menu_remove_field_holo_light.png
index cb993fd..8c44e70 100644
--- a/res/drawable-mdpi/ic_menu_remove_field_holo_light.png
+++ b/res/drawable-mdpi/ic_menu_remove_field_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/change_photo_box_normal_holo_light.9.png b/res/drawable-xhdpi/change_photo_box_normal_holo_light.9.png
new file mode 100644
index 0000000..7e1e97f
--- /dev/null
+++ b/res/drawable-xhdpi/change_photo_box_normal_holo_light.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/change_photo_box_pressed_holo_light.9.png b/res/drawable-xhdpi/change_photo_box_pressed_holo_light.9.png
new file mode 100644
index 0000000..e98266f
--- /dev/null
+++ b/res/drawable-xhdpi/change_photo_box_pressed_holo_light.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_expander_maximized_holo_light.png b/res/drawable-xhdpi/ic_menu_expander_maximized_holo_light.png
index 490371c..6a5ef9b 100644
--- a/res/drawable-xhdpi/ic_menu_expander_maximized_holo_light.png
+++ b/res/drawable-xhdpi/ic_menu_expander_maximized_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_expander_minimized_holo_light.png b/res/drawable-xhdpi/ic_menu_expander_minimized_holo_light.png
index 0e660ea..cb53db8 100644
--- a/res/drawable-xhdpi/ic_menu_expander_minimized_holo_light.png
+++ b/res/drawable-xhdpi/ic_menu_expander_minimized_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_remove_field_holo_light.png b/res/drawable-xhdpi/ic_menu_remove_field_holo_light.png
index 705833e..65a6b7b 100644
--- a/res/drawable-xhdpi/ic_menu_remove_field_holo_light.png
+++ b/res/drawable-xhdpi/ic_menu_remove_field_holo_light.png
Binary files differ
diff --git a/res/layout-sw580dp-w1000dp/contact_detail_fragment.xml b/res/layout-sw580dp-w1000dp/contact_detail_fragment.xml
index b8328ef..a15d98f2 100644
--- a/res/layout-sw580dp-w1000dp/contact_detail_fragment.xml
+++ b/res/layout-sw580dp-w1000dp/contact_detail_fragment.xml
@@ -46,6 +46,7 @@
<ListView android:id="@android:id/list"
android:layout_width="0dip"
android:layout_height="match_parent"
+ android:fadingEdge="none"
android:layout_weight="1"
android:divider="@null"/>
diff --git a/res/layout-sw580dp-w1000dp/contact_detail_list_item.xml b/res/layout-sw580dp-w1000dp/contact_detail_list_item.xml
index b57e85c..0d94622 100644
--- a/res/layout-sw580dp-w1000dp/contact_detail_list_item.xml
+++ b/res/layout-sw580dp-w1000dp/contact_detail_list_item.xml
@@ -63,16 +63,17 @@
<ImageView
android:id="@+id/presence_icon"
- android:layout_width="32dip"
- android:layout_height="@dimen/detail_min_line_item_height"
- android:layout_marginLeft="5dip"
- android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dip"
+ android:layout_gravity="center_vertical"
android:scaleType="centerInside" />
<TextView
android:id="@+id/kind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
android:visibility="gone" />
<TextView
@@ -80,6 +81,7 @@
style="@style/ContactDetailItemType"
android:layout_width="wrap_content"
android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
android:paddingRight="16dip" />
<View
diff --git a/res/layout-sw580dp-w1000dp/contact_detail_updates_fragment.xml b/res/layout-sw580dp-w1000dp/contact_detail_updates_fragment.xml
index 0e89d24..71c2267 100644
--- a/res/layout-sw580dp-w1000dp/contact_detail_updates_fragment.xml
+++ b/res/layout-sw580dp-w1000dp/contact_detail_updates_fragment.xml
@@ -19,4 +19,5 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_social_updates"
+ android:fadingEdge="none"
android:divider="@null"/>
diff --git a/res/layout-sw580dp/contact_detail_fragment.xml b/res/layout-sw580dp/contact_detail_fragment.xml
index f05dc56..9b997e1 100644
--- a/res/layout-sw580dp/contact_detail_fragment.xml
+++ b/res/layout-sw580dp/contact_detail_fragment.xml
@@ -33,6 +33,7 @@
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dip"
+ android:fadingEdge="none"
android:cacheColorHint="#00000000"
android:divider="@null"
/>
diff --git a/res/layout-sw580dp/contact_detail_updates_fragment.xml b/res/layout-sw580dp/contact_detail_updates_fragment.xml
index d73275e..6002151 100644
--- a/res/layout-sw580dp/contact_detail_updates_fragment.xml
+++ b/res/layout-sw580dp/contact_detail_updates_fragment.xml
@@ -25,6 +25,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_social_updates"
+ android:fadingEdge="none"
android:divider="@null"/>
<View
diff --git a/res/layout-sw580dp/contact_editor_fragment.xml b/res/layout-sw580dp/contact_editor_fragment.xml
index 988be2a..572de4f 100644
--- a/res/layout-sw580dp/contact_editor_fragment.xml
+++ b/res/layout-sw580dp/contact_editor_fragment.xml
@@ -26,6 +26,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
+ android:fadingEdge="none"
ex:layout_wideParentWidth="800dip"
ex:layout_wideMarginLeft="128dip"
ex:layout_wideMarginRight="128dip"
diff --git a/res/layout-w470dp/contact_detail_fragment.xml b/res/layout-w470dp/contact_detail_fragment.xml
index 4f26cc5..b523ff8 100644
--- a/res/layout-w470dp/contact_detail_fragment.xml
+++ b/res/layout-w470dp/contact_detail_fragment.xml
@@ -49,6 +49,7 @@
<ListView android:id="@android:id/list"
android:layout_width="0dip"
android:layout_height="match_parent"
+ android:fadingEdge="none"
android:layout_weight="1"
android:divider="@null"/>
@@ -57,6 +58,7 @@
<ScrollView android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="0px"
+ android:fadingEdge="none"
android:visibility="gone">
<TextView android:id="@+id/emptyText"
android:layout_width="match_parent"
diff --git a/res/layout-w470dp/contact_detail_updates_fragment.xml b/res/layout-w470dp/contact_detail_updates_fragment.xml
index 6081cfd..44207fc 100644
--- a/res/layout-w470dp/contact_detail_updates_fragment.xml
+++ b/res/layout-w470dp/contact_detail_updates_fragment.xml
@@ -24,6 +24,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/background_social_updates"
+ android:fadingEdge="none"
android:divider="@null"/>
<View
diff --git a/res/layout/call_log_fragment.xml b/res/layout/call_log_fragment.xml
index ff7dd4b..7cfcef3 100644
--- a/res/layout/call_log_fragment.xml
+++ b/res/layout/call_log_fragment.xml
@@ -37,6 +37,7 @@
<ListView android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fadingEdge="none"
android:scrollbarStyle="outsideOverlay"
/>
<TextView android:id="@android:id/empty"
diff --git a/res/layout/contact_detail_fragment.xml b/res/layout/contact_detail_fragment.xml
index 581dc03..a7b509d 100644
--- a/res/layout/contact_detail_fragment.xml
+++ b/res/layout/contact_detail_fragment.xml
@@ -24,6 +24,7 @@
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1"
+ android:fadingEdge="none"
android:background="@color/background_primary"
android:divider="@null"/>
@@ -31,6 +32,7 @@
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1"
+ android:fadingEdge="none"
android:visibility="gone">
<TextView android:id="@+id/emptyText"
diff --git a/res/layout/contact_detail_list_item.xml b/res/layout/contact_detail_list_item.xml
index e292f39..4d6ed5f 100644
--- a/res/layout/contact_detail_list_item.xml
+++ b/res/layout/contact_detail_list_item.xml
@@ -64,6 +64,15 @@
android:layout_height="wrap_content"
android:orientation="horizontal">
+ <ImageView
+ android:id="@+id/presence_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dip"
+ android:layout_gravity="center_vertical"
+ android:gravity="center"
+ android:scaleType="centerInside" />
+
<TextView
android:id="@+id/type"
style="@style/ContactDetailItemType"
@@ -90,14 +99,6 @@
android:visibility="gone" />
</LinearLayout>
-
- <ImageView
- android:id="@+id/presence_icon"
- android:layout_width="32dip"
- android:layout_height="wrap_content"
- android:layout_marginLeft="5dip"
- android:gravity="center"
- android:scaleType="centerInside" />
</com.android.contacts.detail.PrimaryActionViewContainer>
<View
diff --git a/res/layout/contact_editor_fragment.xml b/res/layout/contact_editor_fragment.xml
index f3989e1..913a5e0 100644
--- a/res/layout/contact_editor_fragment.xml
+++ b/res/layout/contact_editor_fragment.xml
@@ -24,6 +24,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
+ android:fadingEdge="none"
>
<LinearLayout android:id="@+id/editors"
diff --git a/res/layout/contact_tile_list.xml b/res/layout/contact_tile_list.xml
index 1df1377..b1469f3 100644
--- a/res/layout/contact_tile_list.xml
+++ b/res/layout/contact_tile_list.xml
@@ -24,6 +24,7 @@
android:id="@+id/contact_tile_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fadingEdge="none"
android:divider="@null" />
<TextView
diff --git a/res/layout/contact_tile_starred.xml b/res/layout/contact_tile_starred.xml
index 757abea..3f8d91d 100644
--- a/res/layout/contact_tile_starred.xml
+++ b/res/layout/contact_tile_starred.xml
@@ -60,6 +60,7 @@
android:textColor="@color/people_contact_tile_status_color"
android:singleLine="true"
android:drawablePadding="4dip"
+ android:paddingBottom="4dip"
android:fadingEdge="horizontal"
android:fadingEdgeLength="3dip"
android:ellipsize="marquee" />
diff --git a/res/layout/contacts_list_content.xml b/res/layout/contacts_list_content.xml
index cc0ebf4..4223f54 100644
--- a/res/layout/contacts_list_content.xml
+++ b/res/layout/contacts_list_content.xml
@@ -64,6 +64,7 @@
android:layout_marginLeft="?attr/contact_browser_list_padding_left"
android:layout_marginRight="?attr/contact_browser_list_padding_right"
android:fastScrollEnabled="true"
+ android:fadingEdge="none"
android:layout_weight="1" />
<ViewStub
diff --git a/res/layout/group_browse_list_fragment.xml b/res/layout/group_browse_list_fragment.xml
index e3d98f0..c390e67 100644
--- a/res/layout/group_browse_list_fragment.xml
+++ b/res/layout/group_browse_list_fragment.xml
@@ -30,6 +30,7 @@
android:paddingRight="16dip"
android:scrollbarStyle="outsideOverlay"
android:layout_weight="1"
+ android:fadingEdge="none"
android:cacheColorHint="@android:color/transparent"
android:divider="@null" />
diff --git a/res/layout/raw_contact_editor_view.xml b/res/layout/raw_contact_editor_view.xml
index af95e04..66feb27 100644
--- a/res/layout/raw_contact_editor_view.xml
+++ b/res/layout/raw_contact_editor_view.xml
@@ -56,7 +56,7 @@
android:id="@+id/stub_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginRight="8dip">
+ android:layout_marginRight="16dip">
<include
android:id="@+id/edit_photo"
diff --git a/res/layout/updates_title.xml b/res/layout/updates_title.xml
index 4b706cc..78fe178 100644
--- a/res/layout/updates_title.xml
+++ b/res/layout/updates_title.xml
@@ -31,6 +31,7 @@
android:text="@string/recent_updates"
android:textColor="@color/detail_kind_title_color"
android:textStyle="bold"
+ android:textAllCaps="true"
android:singleLine="true"
android:ellipsize="end"
android:paddingLeft="8dip"
diff --git a/res/values-sw580dp/dimens.xml b/res/values-sw580dp/dimens.xml
index 42a8314..440929a 100644
--- a/res/values-sw580dp/dimens.xml
+++ b/res/values-sw580dp/dimens.xml
@@ -31,6 +31,8 @@
<dimen name="action_bar_search_spacing">12dip</dimen>
<dimen name="shortcut_icon_size">64dip</dimen>
<dimen name="list_section_height">37dip</dimen>
+ <dimen name="detail_update_section_side_padding">0dip</dimen>
+ <dimen name="detail_update_section_item_horizontal_padding">8dip</dimen>
<dimen name="detail_update_section_item_vertical_padding">32dip</dimen>
<dimen name="detail_update_section_item_last_row_extra_vertical_padding">16dip</dimen>
<dimen name="search_view_width">400dip</dimen>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 418d632..03a9134 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -168,6 +168,7 @@
<style name="ContactsActionBarStyle" parent="@android:style/Widget.Holo.Light.ActionBar.Solid.Inverse">
<item name="android:background">@drawable/ab_solid_custom_blue_inverse_holo</item>
<item name="android:backgroundStacked">@drawable/ab_solid_custom_blue_inverse_holo</item>
+ <item name="android:displayOptions"></item>
</style>
<style name="ContactsActionBarTabView" parent="@android:style/Widget.Holo.ActionBar.TabView">
@@ -287,6 +288,7 @@
<style name="DialtactsActionBarStyle" parent="android:Widget.Holo.ActionBar">
<item name="android:backgroundSplit">@drawable/ab_bottom_opaque_dark_holo</item>
<item name="android:backgroundStacked">@drawable/ab_stacked_opaque_dark_holo</item>
+ <item name="android:displayOptions"></item>
</style>
</resources>
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index 85e0ff7..4941121 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -20,9 +20,8 @@
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.util.AsyncTaskExecutor;
+import com.android.contacts.util.AsyncTaskExecutors;
import com.android.contacts.voicemail.VoicemailPlaybackFragment;
import com.android.contacts.voicemail.VoicemailStatusHelper;
import com.android.contacts.voicemail.VoicemailStatusHelper.StatusMessage;
@@ -73,6 +72,14 @@
public class CallDetailActivity extends Activity {
private static final String TAG = "CallDetail";
+ /** The enumeration of {@link AsyncTask} objects used in this class. */
+ public enum Tasks {
+ MARK_VOICEMAIL_READ,
+ DELETE_VOICEMAIL_AND_FINISH,
+ REMOVE_FROM_CALL_LOG_AND_FINISH,
+ UPDATE_PHONE_CALL_DETAILS,
+ }
+
/** A long array extra containing ids of call log entries to display. */
public static final String EXTRA_CALL_LOG_IDS = "EXTRA_CALL_LOG_IDS";
/** If we are started with a voicemail, we'll find the uri to play with this extra. */
@@ -87,7 +94,7 @@
private ImageView mMainActionView;
private ImageButton mMainActionPushLayerView;
private ImageView mContactBackgroundView;
- private BackgroundTaskService mBackgroundTaskService;
+ private AsyncTaskExecutor mAsyncTaskExecutor;
private String mNumber = null;
private String mDefaultCountryIso;
@@ -161,8 +168,7 @@
setContentView(R.layout.call_detail);
- mBackgroundTaskService = (BackgroundTaskService) getApplicationContext().getSystemService(
- BackgroundTaskService.BACKGROUND_TASK_SERVICE);
+ mAsyncTaskExecutor = AsyncTaskExecutors.createThreadPoolExecutor();
mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
mResources = getResources();
@@ -231,14 +237,15 @@
}
private void markVoicemailAsRead(final Uri voicemailUri) {
- mBackgroundTaskService.submit(new AbstractBackgroundTask() {
+ mAsyncTaskExecutor.submit(Tasks.MARK_VOICEMAIL_READ, new AsyncTask<Void, Void, Void>() {
@Override
- public void doInBackground() {
+ public Void doInBackground(Void... params) {
ContentValues values = new ContentValues();
values.put(Voicemails.IS_READ, true);
getContentResolver().update(voicemailUri, values, null, null);
+ return null;
}
- }, AsyncTask.THREAD_POOL_EXECUTOR);
+ });
}
/**
@@ -288,28 +295,27 @@
* @param callUris URIs into {@link CallLog.Calls} of the calls to be displayed
*/
private void updateData(final Uri... callUris) {
- mBackgroundTaskService.submit(new BackgroundTask() {
- private PhoneCallDetails[] details;
-
+ class UpdateContactDetailsTask extends AsyncTask<Void, Void, PhoneCallDetails[]> {
@Override
- public void doInBackground() {
+ public PhoneCallDetails[] doInBackground(Void... params) {
// TODO: All phone calls correspond to the same person, so we can make a single
// lookup.
final int numCalls = callUris.length;
- details = new PhoneCallDetails[numCalls];
+ PhoneCallDetails[] details = new PhoneCallDetails[numCalls];
try {
for (int index = 0; index < numCalls; ++index) {
details[index] = getPhoneCallDetailsForUri(callUris[index]);
}
+ return details;
} catch (IllegalArgumentException e) {
// Something went wrong reading in our primary data.
Log.w(TAG, "invalid URI starting call details", e);
- details = null;
+ return null;
}
}
@Override
- public void onPostExecute() {
+ public void onPostExecute(PhoneCallDetails[] details) {
if (details == null) {
// Somewhere went wrong: we're going to bail out and show error to users.
Toast.makeText(CallDetailActivity.this, R.string.toast_call_detail_error,
@@ -461,7 +467,8 @@
loadContactPhotos(photoUri);
findViewById(R.id.call_detail).setVisibility(View.VISIBLE);
}
- });
+ }
+ mAsyncTaskExecutor.submit(Tasks.UPDATE_PHONE_CALL_DETAILS, new UpdateContactDetailsTask());
}
/** Return the phone call details for a given call log URI. */
@@ -707,34 +714,40 @@
}
callIds.append(ContentUris.parseId(callUri));
}
- mBackgroundTaskService.submit(new BackgroundTask() {
- @Override
- public void doInBackground() {
- getContentResolver().delete(Calls.CONTENT_URI_WITH_VOICEMAIL,
- Calls._ID + " IN (" + callIds + ")", null);
- }
- @Override
- public void onPostExecute() {
- finish();
- }
- });
+ mAsyncTaskExecutor.submit(Tasks.REMOVE_FROM_CALL_LOG_AND_FINISH,
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ public Void doInBackground(Void... params) {
+ getContentResolver().delete(Calls.CONTENT_URI_WITH_VOICEMAIL,
+ Calls._ID + " IN (" + callIds + ")", null);
+ return null;
+ }
+
+ @Override
+ public void onPostExecute(Void result) {
+ finish();
+ }
+ });
}
+
public void onMenuEditNumberBeforeCall(MenuItem menuItem) {
startActivity(new Intent(Intent.ACTION_DIAL, mPhoneNumberHelper.getCallUri(mNumber)));
}
public void onMenuTrashVoicemail(MenuItem menuItem) {
final Uri voicemailUri = getVoicemailUri();
- mBackgroundTaskService.submit(new BackgroundTask() {
- @Override
- public void doInBackground() {
- getContentResolver().delete(voicemailUri, null, null);
- }
- @Override
- public void onPostExecute() {
- finish();
- }
- });
+ mAsyncTaskExecutor.submit(Tasks.DELETE_VOICEMAIL_AND_FINISH,
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ public Void doInBackground(Void... params) {
+ getContentResolver().delete(voicemailUri, null, null);
+ return null;
+ }
+ @Override
+ public void onPostExecute(Void result) {
+ finish();
+ }
+ });
}
private void configureActionBar() {
diff --git a/src/com/android/contacts/ContactsApplication.java b/src/com/android/contacts/ContactsApplication.java
index 0aba332..1c8c080 100644
--- a/src/com/android/contacts/ContactsApplication.java
+++ b/src/com/android/contacts/ContactsApplication.java
@@ -16,11 +16,8 @@
package com.android.contacts;
-import static com.android.contacts.util.BackgroundTaskService.createAsyncTaskBackgroundTaskService;
-
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;
@@ -36,7 +33,6 @@
private static InjectedServices sInjectedServices;
private AccountTypeManager mAccountTypeManager;
private ContactPhotoManager mContactPhotoManager;
- private BackgroundTaskService mBackgroundTaskService;
/**
* Overrides the system services with mocks for testing.
@@ -97,13 +93,6 @@
return mContactPhotoManager;
}
- if (BackgroundTaskService.BACKGROUND_TASK_SERVICE.equals(name)) {
- if (mBackgroundTaskService == null) {
- mBackgroundTaskService = createAsyncTaskBackgroundTaskService();
- }
- return mBackgroundTaskService;
- }
-
return super.getSystemService(name);
}
diff --git a/src/com/android/contacts/calllog/CallLogAdapter.java b/src/com/android/contacts/calllog/CallLogAdapter.java
index 7e934b6..28e2e90 100644
--- a/src/com/android/contacts/calllog/CallLogAdapter.java
+++ b/src/com/android/contacts/calllog/CallLogAdapter.java
@@ -24,6 +24,7 @@
import com.android.contacts.util.ExpirableCache;
import com.google.common.annotations.VisibleForTesting;
+import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
@@ -37,6 +38,7 @@
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -47,7 +49,7 @@
/**
* Adapter class to fill in data for the Call Log.
*/
-public final class CallLogAdapter extends GroupingListAdapter
+public class CallLogAdapter extends GroupingListAdapter
implements Runnable, ViewTreeObserver.OnPreDrawListener, CallLogGroupBuilder.GroupCreator {
/** Interface used to initiate a refresh of the content. */
public interface CallFetcher {
@@ -75,10 +77,13 @@
/**
* List of requests to update contact details.
* <p>
+ * Each request is made of a phone number to look up, and the contact info currently stored in
+ * the call log for this number.
+ * <p>
* The requests are added when displaying the contacts and are processed by a background
* thread.
*/
- private final LinkedList<String> mRequests;
+ private final LinkedList<Pair<String, ContactInfo>> mRequests;
private volatile boolean mDone;
private boolean mLoading = true;
@@ -155,7 +160,7 @@
mCallFetcher = callFetcher;
mContactInfoCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
- mRequests = new LinkedList<String>();
+ mRequests = new LinkedList<Pair<String,ContactInfo>>();
mPreDrawListener = null;
Resources resources = mContext.getResources();
@@ -230,10 +235,21 @@
mPreDrawListener = null;
}
- private void enqueueRequest(String number, boolean immediate) {
+ /**
+ * Enqueues a request to look up the contact details for the given phone number.
+ * <p>
+ * It also provides the current contact info stored in the call log for this number.
+ * <p>
+ * If the {@code immediate} parameter is true, it will start immediately the thread that looks
+ * up the contact information (if it has not been already started). Otherwise, it will be
+ * started with a delay. See {@link #START_PROCESSING_REQUESTS_DELAY_MILLIS}.
+ */
+ @VisibleForTesting
+ void enqueueRequest(String number, ContactInfo callLogInfo, boolean immediate) {
+ Pair<String, ContactInfo> request = new Pair<String, ContactInfo>(number, callLogInfo);
synchronized (mRequests) {
- if (!mRequests.contains(number)) {
- mRequests.add(number);
+ if (!mRequests.contains(request)) {
+ mRequests.add(request);
mRequests.notifyAll();
}
}
@@ -378,12 +394,15 @@
/**
* Queries the appropriate content provider for the contact associated with the number.
* <p>
+ * Upon completion it also updates the cache in the call log, if it is different from
+ * {@code callLogInfo}.
+ * <p>
* The number might be either a SIP address or a phone number.
* <p>
* It returns true if it updated the content of the cache and we should therefore tell the
* view to update its content.
*/
- private boolean queryContactInfo(String number) {
+ private boolean queryContactInfo(String number, ContactInfo callLogInfo) {
final ContactInfo info;
// Determine the contact info.
@@ -411,6 +430,9 @@
// Store the data in the cache so that the UI thread can use to display it. Store it
// even if it has not changed so that it is marked as not expired.
mContactInfoCache.put(number, info);
+ // Update the call log even if the cache it is up-to-date: it is possible that the cache
+ // contains the value from a different call log entry.
+ updateCallLogContactInfoCache(number, info, callLogInfo);
return updated;
}
@@ -423,9 +445,12 @@
boolean needNotify = false;
while (!mDone) {
String number = null;
+ ContactInfo callLogInfo = null;
synchronized (mRequests) {
if (!mRequests.isEmpty()) {
- number = mRequests.removeFirst();
+ Pair<String, ContactInfo> request = mRequests.removeFirst();
+ number = request.first;
+ callLogInfo = request.second;
} else {
if (needNotify) {
needNotify = false;
@@ -439,7 +464,7 @@
}
}
}
- if (!mDone && number != null && queryContactInfo(number)) {
+ if (!mDone && number != null && queryContactInfo(number, callLogInfo)) {
needNotify = true;
}
}
@@ -571,14 +596,20 @@
info = ContactInfo.EMPTY;
mContactInfoCache.put(number, info);
// Request the contact details immediately since they are currently missing.
- enqueueRequest(number, true);
+ enqueueRequest(number, cachedContactInfo, true);
// Format the phone number in the call log as best as we can.
formattedNumber = formatPhoneNumber(number, null, countryIso);
} else {
if (cachedInfo.isExpired()) {
// The contact info is no longer up to date, we should request it. However, we
// do not need to request them immediately.
- enqueueRequest(number, false);
+ enqueueRequest(number, cachedContactInfo, false);
+ } else if (!callLogInfoMatches(cachedContactInfo, info)) {
+ // The call log information does not match the one we have, look it up again.
+ // We could simply update the call log directly, but that needs to be done in a
+ // background thread, so it is easier to simply request a new lookup, which will, as
+ // a side-effect, update the call log.
+ enqueueRequest(number, cachedContactInfo, false);
}
if (info != ContactInfo.EMPTY) {
@@ -629,6 +660,52 @@
}
}
+ /** Checks whether the contact info from the call log matches the one from the contacts db. */
+ private boolean callLogInfoMatches(ContactInfo callLogInfo, ContactInfo info) {
+ // The call log only contains a subset of the fields in the contacts db.
+ // Only check those.
+ return TextUtils.equals(callLogInfo.name, info.name)
+ && callLogInfo.type == info.type
+ && TextUtils.equals(callLogInfo.label, info.label);
+ }
+
+ /** Stores the updated contact info in the call log if it is different from the current one. */
+ private void updateCallLogContactInfoCache(String number, ContactInfo updatedInfo,
+ ContactInfo callLogInfo) {
+ final ContentValues values = new ContentValues();
+ boolean needsUpdate = false;
+
+ if (callLogInfo != null) {
+ if (!TextUtils.equals(updatedInfo.name, callLogInfo.name)) {
+ values.put(Calls.CACHED_NAME, updatedInfo.name);
+ needsUpdate = true;
+ }
+
+ if (updatedInfo.type != callLogInfo.type) {
+ values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type);
+ needsUpdate = true;
+ }
+
+ if (!TextUtils.equals(updatedInfo.label, callLogInfo.label)) {
+ values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label);
+ needsUpdate = true;
+ }
+ } else {
+ needsUpdate = true;
+ }
+
+ if (!needsUpdate) {
+ return;
+ }
+
+ StringBuilder where = new StringBuilder();
+ where.append(Calls.NUMBER);
+ where.append(" = ?");
+
+ mContext.getContentResolver().update(Calls.CONTENT_URI_WITH_VOICEMAIL, values,
+ where.toString(), new String[]{ number });
+ }
+
/** Returns the contact information as stored in the call log. */
private ContactInfo getContactInfoFromCallLog(Cursor c) {
ContactInfo info = new ContactInfo();
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index ca99c79..4d50a39 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -43,6 +43,7 @@
import com.android.contacts.util.PhoneCapabilityTester;
import com.android.contacts.widget.TransitionAnimationView;
import com.android.internal.telephony.ITelephony;
+import com.google.common.annotations.VisibleForTesting;
import android.app.Activity;
import android.app.Fragment;
@@ -122,8 +123,6 @@
private static final String TAG = "ContactDetailFragment";
- private static final int LOADER_DETAILS = 1;
-
private interface ContextMenuIds {
static final int COPY_TEXT = 0;
static final int CLEAR_DEFAULT = 1;
@@ -132,7 +131,6 @@
private static final String KEY_CONTACT_URI = "contactUri";
private static final String KEY_LIST_STATE = "liststate";
- private static final String LOADER_ARG_CONTACT_URI = "contactUri";
private Context mContext;
private View mView;
@@ -605,7 +603,7 @@
final DetailViewEntry imEntry = DetailViewEntry.fromValues(mContext, imMime,
imKind, dataId, entryValues, mContactData.isDirectoryEntry(),
mContactData.getDirectoryId());
- buildImActions(imEntry, entryValues);
+ buildImActions(mContext, imEntry, entryValues);
imEntry.applyStatus(status, false);
mImEntries.add(imEntry);
}
@@ -617,7 +615,7 @@
mPostalEntries.add(entry);
} else if (Im.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
// Build IM entries
- buildImActions(entry, entryValues);
+ buildImActions(mContext, entry, entryValues);
// Apply presence and status details when available
final DataStatus status = mContactData.getStatuses().get(entry.id);
@@ -936,11 +934,11 @@
}
/**
- * Build {@link Intent} to launch an action for the given {@link Im} or
- * {@link Email} row. If the result is non-null, it either contains one or two Intents
- * (e.g. [Text, Videochat] or just [Text])
+ * Writes the Instant Messaging action into the given entry value.
*/
- public static void buildImActions(DetailViewEntry entry, ContentValues values) {
+ @VisibleForTesting
+ public static void buildImActions(Context context, DetailViewEntry entry,
+ ContentValues values) {
final boolean isEmail = Email.CONTENT_ITEM_TYPE.equals(values.getAsString(Data.MIMETYPE));
if (!isEmail && !isProtocolValid(values)) {
@@ -958,6 +956,8 @@
final Integer chatCapabilityObj = values.getAsInteger(Im.CHAT_CAPABILITY);
final int chatCapability = chatCapabilityObj == null ? 0 : chatCapabilityObj;
entry.chatCapability = chatCapability;
+ entry.typeString = Im.getProtocolLabel(context.getResources(), Im.PROTOCOL_GOOGLE_TALK,
+ null).toString();
if ((chatCapability & Im.CAPABILITY_HAS_CAMERA) != 0) {
entry.actionIcon = R.drawable.sym_action_talk_holo_light;
entry.intent =
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index e1b4e9f..5a96e7f 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -61,11 +61,9 @@
import android.os.SystemClock;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
-import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.DisplayPhoto;
import android.provider.ContactsContract.Groups;
@@ -79,9 +77,6 @@
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.view.ViewStub;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
@@ -102,7 +97,7 @@
AggregationSuggestionEngine.Listener, AggregationSuggestionView.Listener,
ExternalRawContactEditorView.Listener {
- private static final String TAG = "ContactEditorFragment";
+ private static final String TAG = ContactEditorFragment.class.getSimpleName();
private static final int LOADER_DATA = 1;
private static final int LOADER_GROUPS = 2;
diff --git a/src/com/android/contacts/list/ContactTileAdapter.java b/src/com/android/contacts/list/ContactTileAdapter.java
index b7434b1..2f0f24b 100644
--- a/src/com/android/contacts/list/ContactTileAdapter.java
+++ b/src/com/android/contacts/list/ContactTileAdapter.java
@@ -122,7 +122,7 @@
// Converting padding in dips to padding in pixels
mPaddingInPixels = mContext.getResources()
- .getDimensionPixelOffset(R.dimen.contact_tile_divider_padding);
+ .getDimensionPixelSize(R.dimen.contact_tile_divider_padding);
bindColumnIndices();
}
diff --git a/src/com/android/contacts/list/ContactTileView.java b/src/com/android/contacts/list/ContactTileView.java
index bfc4a2e..7355dbf 100644
--- a/src/com/android/contacts/list/ContactTileView.java
+++ b/src/com/android/contacts/list/ContactTileView.java
@@ -16,13 +16,11 @@
package com.android.contacts.list;
import com.android.contacts.ContactPhotoManager;
-import com.android.contacts.ContactStatusUtil;
import com.android.contacts.R;
import com.android.contacts.list.ContactTileAdapter.ContactEntry;
import android.content.Context;
import android.net.Uri;
-import android.provider.ContactsContract.StatusUpdates;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -91,6 +89,7 @@
* fields in {@link ContactEntry}
*/
public void loadFromContact(ContactEntry entry) {
+
if (entry != null) {
mName.setText(entry.name);
mLookupUri = entry.lookupKey;
diff --git a/src/com/android/contacts/util/AbstractBackgroundTask.java b/src/com/android/contacts/util/AbstractBackgroundTask.java
deleted file mode 100644
index c492e7c..0000000
--- a/src/com/android/contacts/util/AbstractBackgroundTask.java
+++ /dev/null
@@ -1,29 +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.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/AsyncTaskExecutor.java b/src/com/android/contacts/util/AsyncTaskExecutor.java
new file mode 100644
index 0000000..f202949
--- /dev/null
+++ b/src/com/android/contacts/util/AsyncTaskExecutor.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface used to submit {@link AsyncTask} objects to run in the background.
+ * <p>
+ * This interface has a direct parallel with the {@link Executor} interface. It exists to decouple
+ * the mechanics of AsyncTask submission from the description of how that AsyncTask will execute.
+ * <p>
+ * One immediate benefit of this approach is that testing becomes much easier, since it is easy to
+ * introduce a mock or fake AsyncTaskExecutor in unit/integration tests, and thus inspect which
+ * tasks have been submitted and control their execution in an orderly manner.
+ * <p>
+ * Another benefit in due course will be the management of the submitted tasks. An extension to this
+ * interface is planned to allow Activities to easily cancel all the submitted tasks that are still
+ * pending in the onDestroy() method of the Activity.
+ */
+public interface AsyncTaskExecutor {
+ /**
+ * Executes the given AsyncTask with the default Executor.
+ * <p>
+ * This method <b>must only be called from the ui thread</b>.
+ * <p>
+ * The identifier supplied is any Object that can be used to identify the task later. Most
+ * commonly this will be an enum which the tests can also refer to. {@code null} is also
+ * accepted, though of course this won't help in identifying the task later.
+ */
+ <T> AsyncTask<T, ?, ?> submit(Object identifier, AsyncTask<T, ?, ?> task, T... params);
+}
diff --git a/src/com/android/contacts/util/AsyncTaskExecutors.java b/src/com/android/contacts/util/AsyncTaskExecutors.java
new file mode 100644
index 0000000..539dee7
--- /dev/null
+++ b/src/com/android/contacts/util/AsyncTaskExecutors.java
@@ -0,0 +1,100 @@
+/*
+ * 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.test.NeededForTesting;
+import com.google.common.base.Preconditions;
+
+import android.os.AsyncTask;
+import android.os.Looper;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Factory methods for creating AsyncTaskExecutors.
+ * <p>
+ * All of the factory methods on this class check first to see if you have set a static
+ * {@link AsyncTaskExecutorFactory} set through the
+ * {@link #setFactoryForTest(AsyncTaskExecutorFactory)} method, and if so delegate to that instead,
+ * which is one way of injecting dependencies for testing classes whose construction cannot be
+ * controlled such as {@link android.app.Activity}.
+ */
+public final class AsyncTaskExecutors {
+ /**
+ * A single instance of the {@link AsyncTaskExecutorFactory}, to which we delegate if it is
+ * non-null, for injecting when testing.
+ */
+ private static AsyncTaskExecutorFactory mInjectedAsyncTaskExecutorFactory = null;
+
+ /**
+ * Creates an AsyncTaskExecutor that submits tasks to run with
+ * {@link AsyncTask#SERIAL_EXECUTOR}.
+ */
+ public static AsyncTaskExecutor createAsyncTaskExecutor() {
+ synchronized (AsyncTaskExecutors.class) {
+ if (mInjectedAsyncTaskExecutorFactory != null) {
+ return mInjectedAsyncTaskExecutorFactory.createAsyncTaskExeuctor();
+ }
+ return new SimpleAsyncTaskExecutor(AsyncTask.SERIAL_EXECUTOR);
+ }
+ }
+
+ /**
+ * Creates an AsyncTaskExecutor that submits tasks to run with
+ * {@link AsyncTask#THREAD_POOL_EXECUTOR}.
+ */
+ public static AsyncTaskExecutor createThreadPoolExecutor() {
+ synchronized (AsyncTaskExecutors.class) {
+ if (mInjectedAsyncTaskExecutorFactory != null) {
+ return mInjectedAsyncTaskExecutorFactory.createAsyncTaskExeuctor();
+ }
+ return new SimpleAsyncTaskExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+ }
+
+ /** Interface for creating AsyncTaskExecutor objects. */
+ public interface AsyncTaskExecutorFactory {
+ AsyncTaskExecutor createAsyncTaskExeuctor();
+ }
+
+ @NeededForTesting
+ public static void setFactoryForTest(AsyncTaskExecutorFactory factory) {
+ synchronized (AsyncTaskExecutors.class) {
+ mInjectedAsyncTaskExecutorFactory = factory;
+ }
+ }
+
+ public static void checkCalledFromUiThread() {
+ Preconditions.checkState(Thread.currentThread() == Looper.getMainLooper().getThread(),
+ "submit method must be called from ui thread, was: " + Thread.currentThread());
+ }
+
+ private static class SimpleAsyncTaskExecutor implements AsyncTaskExecutor {
+ private final Executor mExecutor;
+
+ public SimpleAsyncTaskExecutor(Executor executor) {
+ mExecutor = executor;
+ }
+
+ @Override
+ public <T> AsyncTask<T, ?, ?> submit(Object identifer, AsyncTask<T, ?, ?> task,
+ T... params) {
+ checkCalledFromUiThread();
+ return task.executeOnExecutor(mExecutor, params);
+ }
+ }
+}
diff --git a/src/com/android/contacts/util/BackgroundTask.java b/src/com/android/contacts/util/BackgroundTask.java
deleted file mode 100644
index ba791fb..0000000
--- a/src/com/android/contacts/util/BackgroundTask.java
+++ /dev/null
@@ -1,31 +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.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
deleted file mode 100644
index 310a178..0000000
--- a/src/com/android/contacts/util/BackgroundTaskService.java
+++ /dev/null
@@ -1,83 +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.contacts.util;
-
-import android.os.AsyncTask;
-
-import java.util.concurrent.Executor;
-
-/**
- * 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.
- * <p>
- * You are not expected to derive from this class unless you are writing your own test
- * implementation, or you are absolutely sure that the instance in
- * {@link #createAsyncTaskBackgroundTaskService()} doesn't do what you need.
- */
-public abstract class BackgroundTaskService {
- public static final String BACKGROUND_TASK_SERVICE = BackgroundTaskService.class.getName();
-
- /**
- * Executes the given BackgroundTask with the default Executor.
- * <p>
- * All {@link BackgroundTask#doInBackground()} tasks will be guaranteed to happen serially.
- * If this is not what you want, see {@link #submit(BackgroundTask, Executor)}.
- */
- public abstract void submit(BackgroundTask task);
-
- /**
- * Executes the BackgroundTask with the supplied Executor.
- * <p>
- * The main use-case for this method will be to allow submitted tasks to perform their
- * {@link BackgroundTask#doInBackground()} methods concurrently.
- */
- public abstract void submit(BackgroundTask task, Executor executor);
-
- /**
- * Creates a concrete BackgroundTaskService whose default Executor is
- * {@link AsyncTask#SERIAL_EXECUTOR}.
- */
- public static BackgroundTaskService createAsyncTaskBackgroundTaskService() {
- return new AsyncTaskBackgroundTaskService();
- }
-
- private static final class AsyncTaskBackgroundTaskService extends BackgroundTaskService {
- @Override
- public void submit(BackgroundTask task) {
- submit(task, AsyncTask.SERIAL_EXECUTOR);
- }
-
- @Override
- public void submit(final BackgroundTask task, Executor executor) {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- task.doInBackground();
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- task.onPostExecute();
- }
- }.executeOnExecutor(executor);
- }
- }
-}
diff --git a/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java b/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java
index 5e53b76..5eb0ddf 100644
--- a/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java
+++ b/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java
@@ -21,7 +21,7 @@
import com.android.common.io.MoreCloseables;
import com.android.contacts.R;
-import com.android.contacts.util.BackgroundTaskService;
+import com.android.contacts.util.AsyncTaskExecutors;
import com.android.ex.variablespeed.MediaPlayerProxy;
import com.android.ex.variablespeed.VariableSpeed;
import com.google.common.base.Preconditions;
@@ -93,15 +93,11 @@
boolean startPlayback = arguments.getBoolean(EXTRA_VOICEMAIL_START_PLAYBACK, false);
mPresenter = new VoicemailPlaybackPresenter(createPlaybackViewImpl(),
createMediaPlayer(mScheduledExecutorService), voicemailUri,
- mScheduledExecutorService, startPlayback, getBackgroundTaskService());
+ mScheduledExecutorService, startPlayback,
+ AsyncTaskExecutors.createAsyncTaskExecutor());
mPresenter.onCreate(savedInstanceState);
}
- private BackgroundTaskService getBackgroundTaskService() {
- return (BackgroundTaskService) getActivity().getApplicationContext().getSystemService(
- BackgroundTaskService.BACKGROUND_TASK_SERVICE);
- }
-
@Override
public void onSaveInstanceState(Bundle outState) {
mPresenter.onSaveInstanceState(outState);
diff --git a/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
index bd01991..d3e4bef 100644
--- a/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
+++ b/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
@@ -19,10 +19,10 @@
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.contacts.util.AsyncTaskExecutor;
import com.android.ex.variablespeed.MediaPlayerProxy;
import com.android.ex.variablespeed.SingleThreadedMediaPlayerProxy;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import android.content.Context;
@@ -57,7 +57,8 @@
* the main ui thread.
*/
@NotThreadSafe
-/*package*/ class VoicemailPlaybackPresenter {
+@VisibleForTesting
+public class VoicemailPlaybackPresenter {
/** Contract describing the behaviour we need from the ui we are controlling. */
public interface PlaybackView {
Context getDataSourceContext();
@@ -87,6 +88,13 @@
void unregisterContentObserver(ContentObserver observer);
}
+ /** The enumeration of {@link AsyncTask} objects we use in this class. */
+ public enum Tasks {
+ CHECK_FOR_CONTENT,
+ CHECK_CONTENT_AFTER_CHANGE,
+ PREPARE_MEDIA_PLAYER,
+ }
+
/** Update rate for the slider, 30fps. */
private static final int SLIDER_UPDATE_PERIOD_MILLIS = 1000 / 30;
/** Time our ui will wait for content to be fetched before reporting not available. */
@@ -143,8 +151,8 @@
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;
+ /** Used to run async tasks that need to interact with the ui. */
+ private final AsyncTaskExecutor mAsyncTaskExecutor;
/**
* Used to handle the result of a successful or time-out fetch result.
@@ -155,12 +163,12 @@
public VoicemailPlaybackPresenter(PlaybackView view, MediaPlayerProxy player,
Uri voicemailUri, ScheduledExecutorService executorService,
- boolean startPlayingImmediately, BackgroundTaskService backgroundTaskService) {
+ boolean startPlayingImmediately, AsyncTaskExecutor asyncTaskExecutor) {
mView = view;
mPlayer = player;
mVoicemailUri = voicemailUri;
mStartPlayingImmediately = startPlayingImmediately;
- mBackgroundTaskService = backgroundTaskService;
+ mAsyncTaskExecutor = asyncTaskExecutor;
mPositionUpdater = new PositionUpdater(executorService, SLIDER_UPDATE_PERIOD_MILLIS);
}
@@ -181,23 +189,21 @@
*/
private void checkThatWeHaveContent() {
mView.setIsFetchingContent();
- mBackgroundTaskService.submit(new BackgroundTask() {
- private boolean mHasContent = false;
-
+ mAsyncTaskExecutor.submit(Tasks.CHECK_FOR_CONTENT, new AsyncTask<Void, Void, Boolean>() {
@Override
- public void doInBackground() {
- mHasContent = mView.queryHasContent(mVoicemailUri);
+ public Boolean doInBackground(Void... params) {
+ return mView.queryHasContent(mVoicemailUri);
}
@Override
- public void onPostExecute() {
- if (mHasContent) {
+ public void onPostExecute(Boolean hasContent) {
+ if (hasContent) {
postSuccessfullyFetchedContent();
} else {
makeRequestForContent();
}
}
- }, AsyncTask.THREAD_POOL_EXECUTOR);
+ });
}
/**
@@ -253,24 +259,23 @@
@Override
public void onChange(boolean selfChange) {
- mBackgroundTaskService.submit(new BackgroundTask() {
- private boolean mHasContent = false;
-
+ mAsyncTaskExecutor.submit(Tasks.CHECK_CONTENT_AFTER_CHANGE,
+ new AsyncTask<Void, Void, Boolean>() {
@Override
- public void doInBackground() {
- mHasContent = mView.queryHasContent(mVoicemailUri);
+ public Boolean doInBackground(Void... params) {
+ return mView.queryHasContent(mVoicemailUri);
}
@Override
- public void onPostExecute() {
- if (mHasContent) {
+ public void onPostExecute(Boolean hasContent) {
+ if (hasContent) {
if (mResultStillPending.getAndSet(false)) {
mView.unregisterContentObserver(FetchResultHandler.this);
postSuccessfullyFetchedContent();
}
}
}
- }, AsyncTask.THREAD_POOL_EXECUTOR);
+ });
}
}
@@ -286,29 +291,29 @@
*/
private void postSuccessfullyFetchedContent() {
mView.setIsBuffering();
- mBackgroundTaskService.submit(new BackgroundTask() {
- private Exception mException;
+ mAsyncTaskExecutor.submit(Tasks.PREPARE_MEDIA_PLAYER,
+ new AsyncTask<Void, Void, Exception>() {
+ @Override
+ public Exception doInBackground(Void... params) {
+ try {
+ mPlayer.reset();
+ mPlayer.setDataSource(mView.getDataSourceContext(), mVoicemailUri);
+ mPlayer.prepare();
+ return null;
+ } catch (IOException e) {
+ return e;
+ }
+ }
- @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);
- }
- }
- }, AsyncTask.THREAD_POOL_EXECUTOR);
+ @Override
+ public void onPostExecute(Exception exception) {
+ if (exception == null) {
+ postSuccessfulPrepareActions();
+ } else {
+ mView.playbackError(exception);
+ }
+ }
+ });
}
/**
diff --git a/tests/src/com/android/contacts/CallDetailActivityTest.java b/tests/src/com/android/contacts/CallDetailActivityTest.java
index c060af5..6a8fcb3 100644
--- a/tests/src/com/android/contacts/CallDetailActivityTest.java
+++ b/tests/src/com/android/contacts/CallDetailActivityTest.java
@@ -16,23 +16,24 @@
package com.android.contacts;
-import com.android.contacts.test.InjectedServices;
-import com.android.contacts.util.BackgroundTaskService;
+import static com.android.contacts.CallDetailActivity.Tasks.UPDATE_PHONE_CALL_DETAILS;
+import static com.android.contacts.voicemail.VoicemailPlaybackPresenter.Tasks.CHECK_FOR_CONTENT;
+import static com.android.contacts.voicemail.VoicemailPlaybackPresenter.Tasks.PREPARE_MEDIA_PLAYER;
+
+import com.android.contacts.util.AsyncTaskExecutors;
+import com.android.contacts.util.FakeAsyncTaskExecutor;
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;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Intent;
import android.net.Uri;
import android.provider.CallLog;
-import android.provider.VoicemailContract.Voicemails;
+import android.provider.VoicemailContract;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.Suppress;
@@ -47,12 +48,12 @@
*/
@LargeTest
public class CallDetailActivityTest extends ActivityInstrumentationTestCase2<CallDetailActivity> {
- private static final String FAKE_VOICEMAIL_URI_STRING = ContentUris.withAppendedId(
- Voicemails.CONTENT_URI, Integer.MAX_VALUE).toString();
- private Uri mUri;
+ private Uri mCallLogUri;
+ private Uri mVoicemailUri;
private IntegrationTestUtils mTestUtils;
private LocaleTestUtils mLocaleTestUtils;
- private FakeBackgroundTaskService mMockBackgroundTaskService;
+ private FakeAsyncTaskExecutor mFakeAsyncTaskExecutor;
+ private CallDetailActivity mActivityUnderTest;
public CallDetailActivityTest() {
super(CallDetailActivity.class);
@@ -61,11 +62,8 @@
@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);
+ mFakeAsyncTaskExecutor = new FakeAsyncTaskExecutor(getInstrumentation());
+ AsyncTaskExecutors.setFactoryForTest(mFakeAsyncTaskExecutor.getFactory());
// I don't like the default of focus-mode for tests, the green focus border makes the
// screenshots look weak.
setActivityInitialTouchMode(true);
@@ -82,45 +80,58 @@
mLocaleTestUtils = null;
cleanUpUri();
mTestUtils = null;
- ContactsApplication.injectServices(null);
+ AsyncTaskExecutors.setFactoryForTest(null);
super.tearDown();
}
- public void testInitialActivityStartsWithBuffering() throws Throwable {
+ public void testInitialActivityStartsWithFetchingVoicemail() throws Throwable {
setActivityIntentForTestVoicemailEntry();
- CallDetailActivity activity = getActivity();
- // When the activity first starts, we will show "buffering..." on the screen.
+ startActivityUnderTest();
+ // When the activity first starts, we will show "Fetching voicemail" on the screen.
// The duration should not be visible.
- assertHasOneTextViewContaining(activity, "buffering...");
- assertZeroTextViewsContaining(activity, "00:00");
+ assertHasOneTextViewContaining("Fetching voicemail");
+ assertZeroTextViewsContaining("00:00");
+ }
+
+ public void testWhenCheckForContentCompletes_UiShowsBuffering() throws Throwable {
+ setActivityIntentForTestVoicemailEntry();
+ startActivityUnderTest();
+ // There is a background check that is testing to see if we have the content available.
+ // Once that task completes, we shouldn't be showing the fetching message, we should
+ // be showing "Buffering".
+ mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
+ assertHasOneTextViewContaining("Buffering");
+ assertZeroTextViewsContaining("Fetching voicemail");
}
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.
+ startActivityUnderTest();
+ mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
+ // There should be exactly one background task ready to prepare 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...");
+ mFakeAsyncTaskExecutor.runTask(PREPARE_MEDIA_PLAYER);
+ assertHasOneTextViewContaining("Couldn't play voicemail");
+ assertZeroTextViewsContaining("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.
+ // Before the fix, this was failing reproducibly with at least 3 "Buffering" views.
setActivityIntentForTestVoicemailEntry();
- final CallDetailActivity activity = getActivity();
+ startActivityUnderTest();
+ mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
- getInstrumentation().callActivityOnPause(activity);
- getInstrumentation().callActivityOnResume(activity);
- getInstrumentation().callActivityOnPause(activity);
- getInstrumentation().callActivityOnResume(activity);
+ getInstrumentation().callActivityOnPause(mActivityUnderTest);
+ getInstrumentation().callActivityOnResume(mActivityUnderTest);
+ getInstrumentation().callActivityOnPause(mActivityUnderTest);
+ getInstrumentation().callActivityOnResume(mActivityUnderTest);
}
});
- assertHasOneTextViewContaining(activity, "buffering...");
+ assertHasOneTextViewContaining("Buffering");
}
/**
@@ -132,15 +143,15 @@
*/
public void testClickIncreaseRateButtonWithInvalidVoicemailDoesNotCrash() throws Throwable {
setActivityIntentForTestVoicemailEntry();
- Activity activity = getActivity();
- mTestUtils.clickButton(activity, R.id.playback_start_stop);
- mTestUtils.clickButton(activity, R.id.rate_increase_button);
+ startActivityUnderTest();
+ mTestUtils.clickButton(mActivityUnderTest, R.id.playback_start_stop);
+ mTestUtils.clickButton(mActivityUnderTest, R.id.rate_increase_button);
}
/** Test for bug where missing Extras on intent used to start Activity causes NPE. */
- public void testCallLogUriWithMissingExtrasShouldNotCauseNPE() throws Exception {
+ public void testCallLogUriWithMissingExtrasShouldNotCauseNPE() throws Throwable {
setActivityIntentForTestCallEntry();
- getActivity();
+ startActivityUnderTest();
}
/**
@@ -150,20 +161,20 @@
*/
public void testVoicemailDoesNotHaveRemoveFromCallLog() throws Throwable {
setActivityIntentForTestVoicemailEntry();
- CallDetailActivity activity = getActivity();
- Menu menu = new ContextMenuBuilder(activity);
- activity.onCreateOptionsMenu(menu);
- activity.onPrepareOptionsMenu(menu);
+ startActivityUnderTest();
+ Menu menu = new ContextMenuBuilder(mActivityUnderTest);
+ mActivityUnderTest.onCreateOptionsMenu(menu);
+ mActivityUnderTest.onPrepareOptionsMenu(menu);
assertFalse(menu.findItem(R.id.menu_remove_from_call_log).isVisible());
}
/** Test to check that I haven't broken the remove-from-call-log entry from regular calls. */
public void testRegularCallDoesHaveRemoveFromCallLog() throws Throwable {
setActivityIntentForTestCallEntry();
- CallDetailActivity activity = getActivity();
- Menu menu = new ContextMenuBuilder(activity);
- activity.onCreateOptionsMenu(menu);
- activity.onPrepareOptionsMenu(menu);
+ startActivityUnderTest();
+ Menu menu = new ContextMenuBuilder(mActivityUnderTest);
+ mActivityUnderTest.onCreateOptionsMenu(menu);
+ mActivityUnderTest.onPrepareOptionsMenu(menu);
assertTrue(menu.findItem(R.id.menu_remove_from_call_log).isVisible());
}
@@ -175,16 +186,16 @@
@Suppress
public void testVoicemailPlaybackRateDisplayedOnUi() throws Throwable {
setActivityIntentForTestVoicemailEntry();
- CallDetailActivity activity = getActivity();
+ startActivityUnderTest();
// Find the TextView containing the duration. It should be initially displaying "00:00".
- List<TextView> views = mTestUtils.getTextViewsWithString(activity, "00:00");
+ List<TextView> views = mTestUtils.getTextViewsWithString(mActivityUnderTest, "00:00");
assertEquals(1, views.size());
TextView timeDisplay = views.get(0);
// Hit the plus button. At this point we should be displaying "fast speed".
- mTestUtils.clickButton(activity, R.id.rate_increase_button);
+ mTestUtils.clickButton(mActivityUnderTest, R.id.rate_increase_button);
assertEquals("fast speed", mTestUtils.getText(timeDisplay));
// Hit the minus button. We should be back to "normal" speed.
- mTestUtils.clickButton(activity, R.id.rate_decrease_button);
+ mTestUtils.clickButton(mActivityUnderTest, R.id.rate_decrease_button);
assertEquals("normal speed", mTestUtils.getText(timeDisplay));
// Wait for one and a half seconds. The timer will be back.
Thread.sleep(1500);
@@ -192,39 +203,39 @@
}
private void setActivityIntentForTestCallEntry() {
- createTestCallEntry(false);
- setActivityIntent(new Intent(Intent.ACTION_VIEW, mUri));
+ Preconditions.checkState(mCallLogUri == null, "mUri should be null");
+ ContentResolver contentResolver = getContentResolver();
+ ContentValues values = new ContentValues();
+ values.put(CallLog.Calls.NUMBER, "01234567890");
+ values.put(CallLog.Calls.TYPE, CallLog.Calls.INCOMING_TYPE);
+ mCallLogUri = contentResolver.insert(CallLog.Calls.CONTENT_URI, values);
+ setActivityIntent(new Intent(Intent.ACTION_VIEW, mCallLogUri));
}
private void setActivityIntentForTestVoicemailEntry() {
- createTestCallEntry(true);
- Intent intent = new Intent(Intent.ACTION_VIEW, mUri);
- Uri voicemailUri = Uri.parse(FAKE_VOICEMAIL_URI_STRING);
- intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI, voicemailUri);
+ Preconditions.checkState(mVoicemailUri == null, "mUri should be null");
+ ContentResolver contentResolver = getContentResolver();
+ ContentValues values = new ContentValues();
+ values.put(VoicemailContract.Voicemails.NUMBER, "01234567890");
+ values.put(VoicemailContract.Voicemails.HAS_CONTENT, 1);
+ mVoicemailUri = contentResolver.insert(VoicemailContract.Voicemails.CONTENT_URI, values);
+ Uri callLogUri = ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
+ ContentUris.parseId(mVoicemailUri));
+ Intent intent = new Intent(Intent.ACTION_VIEW, callLogUri);
+ intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI, mVoicemailUri);
setActivityIntent(intent);
}
- /** Inserts an entry into the call log. */
- private void createTestCallEntry(boolean isVoicemail) {
- Preconditions.checkState(mUri == null, "mUri should be null");
- ContentResolver contentResolver = getContentResolver();
- ContentValues contentValues = new ContentValues();
- contentValues.put(CallLog.Calls.NUMBER, "01234567890");
- if (isVoicemail) {
- contentValues.put(CallLog.Calls.TYPE, CallLog.Calls.VOICEMAIL_TYPE);
- contentValues.put(CallLog.Calls.VOICEMAIL_URI, FAKE_VOICEMAIL_URI_STRING);
- } else {
- contentValues.put(CallLog.Calls.TYPE, CallLog.Calls.INCOMING_TYPE);
- }
- contentValues.put(CallLog.Calls.VOICEMAIL_URI, FAKE_VOICEMAIL_URI_STRING);
- mUri = contentResolver.insert(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL, contentValues);
- }
-
private void cleanUpUri() {
- if (mUri != null) {
+ if (mVoicemailUri != null) {
+ getContentResolver().delete(VoicemailContract.Voicemails.CONTENT_URI,
+ "_ID = ?", new String[] { String.valueOf(ContentUris.parseId(mVoicemailUri)) });
+ mVoicemailUri = null;
+ }
+ if (mCallLogUri != null) {
getContentResolver().delete(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
- "_ID = ?", new String[] { String.valueOf(ContentUris.parseId(mUri)) });
- mUri = null;
+ "_ID = ?", new String[] { String.valueOf(ContentUris.parseId(mCallLogUri)) });
+ mCallLogUri = null;
}
}
@@ -232,23 +243,28 @@
return getInstrumentation().getTargetContext().getContentResolver();
}
- private TextView assertHasOneTextViewContaining(Activity activity, String text)
- throws Throwable {
- List<TextView> views = mTestUtils.getTextViewsWithString(activity, text);
+ private TextView assertHasOneTextViewContaining(String text) throws Throwable {
+ Preconditions.checkNotNull(mActivityUnderTest, "forget to call startActivityUnderTest()?");
+ List<TextView> views = mTestUtils.getTextViewsWithString(mActivityUnderTest, 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);
+ private void assertZeroTextViewsContaining(String text) throws Throwable {
+ Preconditions.checkNotNull(mActivityUnderTest, "forget to call startActivityUnderTest()?");
+ List<TextView> views = mTestUtils.getTextViewsWithString(mActivityUnderTest, 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()));
+ private void startActivityUnderTest() throws Throwable {
+ Preconditions.checkState(mActivityUnderTest == null, "must only start the activity once");
+ mActivityUnderTest = getActivity();
+ assertNotNull("activity should not be null", mActivityUnderTest);
+ // We have to run all tasks, not just one.
+ // This is because it seems that we can have onResume, onPause, onResume during the course
+ // of a single unit test.
+ mFakeAsyncTaskExecutor.runAllTasks(UPDATE_PHONE_CALL_DETAILS);
}
}
diff --git a/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java b/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java
new file mode 100644
index 0000000..28db896
--- /dev/null
+++ b/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.calllog;
+
+import com.google.common.collect.Lists;
+
+import android.content.Context;
+import android.database.MatrixCursor;
+import android.test.AndroidTestCase;
+import android.view.View;
+
+import java.util.List;
+
+/**
+ * Unit tests for {@link CallLogAdapter}.
+ */
+public class CallLogAdapterTest extends AndroidTestCase {
+ private static final String TEST_NUMBER = "12345678";
+ private static final String TEST_NAME = "name";
+ private static final String TEST_NUMBER_LABEL = "label";
+ private static final int TEST_NUMBER_TYPE = 1;
+ private static final String TEST_COUNTRY_ISO = "US";
+ private static final String TEST_VOICEMAIL_NUMBER = "111";
+
+ /** The object under test. */
+ private TestCallLogAdapter mAdapter;
+
+ private MatrixCursor mCursor;
+ private View mView;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // Use a call fetcher that does not do anything.
+ CallLogAdapter.CallFetcher fakeCallFetcher = new CallLogAdapter.CallFetcher() {
+ @Override
+ public void startCallsQuery() {}
+ };
+
+ mAdapter = new TestCallLogAdapter(getContext(), fakeCallFetcher, TEST_COUNTRY_ISO,
+ TEST_VOICEMAIL_NUMBER);
+ // The cursor used in the tests to store the entries to display.
+ mCursor = new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
+ mCursor.moveToFirst();
+ // The views into which to store the data.
+ mView = new View(getContext());
+ mView.setTag(CallLogListItemViews.createForTest(getContext()));
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mAdapter = null;
+ mCursor = null;
+ mView = null;
+ super.tearDown();
+ }
+
+ public void testBindView_NoCallLogCacheNorMemoryCache_EnqueueRequest() {
+ mCursor.addRow(createCallLogEntry());
+
+ // Bind the views of a single row.
+ mAdapter.bindStandAloneView(mView, getContext(), mCursor);
+
+ // There is one request for contact details.
+ assertEquals(1, mAdapter.requests.size());
+
+ TestCallLogAdapter.Request request = mAdapter.requests.get(0);
+ // It is for the number we need to show.
+ assertEquals(TEST_NUMBER, request.number);
+ // Since there is nothing in the cache, it is an immediate request.
+ assertTrue("should be immediate", request.immediate);
+ }
+
+ public void testBindView_CallLogCacheButNoMemoryCache_EnqueueRequest() {
+ mCursor.addRow(createCallLogEntryWithCachedValues());
+
+ // Bind the views of a single row.
+ mAdapter.bindStandAloneView(mView, getContext(), mCursor);
+
+ // There is one request for contact details.
+ assertEquals(1, mAdapter.requests.size());
+
+ TestCallLogAdapter.Request request = mAdapter.requests.get(0);
+ // The values passed to the request, match the ones in the call log cache.
+ assertEquals(TEST_NAME, request.callLogInfo.name);
+ assertEquals(1, request.callLogInfo.type);
+ assertEquals(TEST_NUMBER_LABEL, request.callLogInfo.label);
+ }
+
+
+ public void testBindView_NoCallLogButMemoryCache_EnqueueRequest() {
+ mCursor.addRow(createCallLogEntry());
+ mAdapter.injectContactInfoForTest(TEST_NUMBER, createContactInfo());
+
+ // Bind the views of a single row.
+ mAdapter.bindStandAloneView(mView, getContext(), mCursor);
+
+ // There is one request for contact details.
+ assertEquals(1, mAdapter.requests.size());
+
+ TestCallLogAdapter.Request request = mAdapter.requests.get(0);
+ // Since there is something in the cache, it is not an immediate request.
+ assertFalse("should not be immediate", request.immediate);
+ }
+
+ public void testBindView_BothCallLogAndMemoryCache_NoEnqueueRequest() {
+ mCursor.addRow(createCallLogEntryWithCachedValues());
+ mAdapter.injectContactInfoForTest(TEST_NUMBER, createContactInfo());
+
+ // Bind the views of a single row.
+ mAdapter.bindStandAloneView(mView, getContext(), mCursor);
+
+ // Cache and call log are up-to-date: no need to request update.
+ assertEquals(0, mAdapter.requests.size());
+ }
+
+ public void testBindView_MismatchBetwenCallLogAndMemoryCache_EnqueueRequest() {
+ mCursor.addRow(createCallLogEntryWithCachedValues());
+
+ // Contact info contains a different name.
+ ContactInfo info = createContactInfo();
+ info.name = "new name";
+ mAdapter.injectContactInfoForTest(TEST_NUMBER, info);
+
+ // Bind the views of a single row.
+ mAdapter.bindStandAloneView(mView, getContext(), mCursor);
+
+ // There is one request for contact details.
+ assertEquals(1, mAdapter.requests.size());
+
+ TestCallLogAdapter.Request request = mAdapter.requests.get(0);
+ // Since there is something in the cache, it is not an immediate request.
+ assertFalse("should not be immediate", request.immediate);
+ }
+
+ /** Returns a contact info with default values. */
+ private ContactInfo createContactInfo() {
+ ContactInfo info = new ContactInfo();
+ info.number = TEST_NUMBER;
+ info.name = TEST_NAME;
+ info.type = TEST_NUMBER_TYPE;
+ info.label = TEST_NUMBER_LABEL;
+ return info;
+ }
+
+ /** Returns a call log entry without cached values. */
+ private Object[] createCallLogEntry() {
+ Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
+ values[CallLogQuery.NUMBER] = TEST_NUMBER;
+ return values;
+ }
+
+ /** Returns a call log entry with a cached values. */
+ private Object[] createCallLogEntryWithCachedValues() {
+ Object[] values = createCallLogEntry();
+ values[CallLogQuery.CACHED_NAME] = TEST_NAME;
+ values[CallLogQuery.CACHED_NUMBER_TYPE] = TEST_NUMBER_TYPE;
+ values[CallLogQuery.CACHED_NUMBER_LABEL] = TEST_NUMBER_LABEL;
+ return values;
+ }
+
+ /**
+ * Subclass of {@link CallLogAdapter} used in tests to intercept certain calls.
+ */
+ // TODO: This would be better done by splitting the contact lookup into a collaborator class
+ // instead.
+ private static final class TestCallLogAdapter extends CallLogAdapter {
+ public static class Request {
+ public final String number;
+ public final ContactInfo callLogInfo;
+ public final boolean immediate;
+
+ public Request(String number, ContactInfo callLogInfo, boolean immediate) {
+ this.number = number;
+ this.callLogInfo = callLogInfo;
+ this.immediate = immediate;
+ }
+ }
+
+ public final List<Request> requests = Lists.newArrayList();
+
+ public TestCallLogAdapter(Context context, CallFetcher callFetcher,
+ String currentCountryIso, String voicemailNumber) {
+ super(context, callFetcher, currentCountryIso, voicemailNumber);
+ }
+
+ @Override
+ void enqueueRequest(String number, ContactInfo callLogInfo, boolean immediate) {
+ requests.add(new Request(number, callLogInfo, immediate));
+ }
+ }
+}
diff --git a/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java b/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java
index 0e1952a..c273612 100644
--- a/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java
+++ b/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java
@@ -17,6 +17,9 @@
package com.android.contacts.calllog;
import static junit.framework.Assert.assertEquals;
+
+import android.provider.CallLog.Calls;
+
import junit.framework.Assert;
/**
@@ -24,13 +27,18 @@
*/
public class CallLogQueryTestUtils {
public static Object[] createTestValues() {
- Object[] values = new Object[]{ -1L, "", 0L, 0L, 0, "", "", "", null, 0, null };
+ Object[] values = new Object[]{
+ -1L, "", 0L, 0L, Calls.INCOMING_TYPE, "", "", "", null, 0, null,
+ };
assertEquals(CallLogQuery._PROJECTION.length, values.length);
return values;
}
public static Object[] createTestExtendedValues() {
- Object[] values = new Object[]{ -1L, "", 0L, 0L, 0, "", "", "", null, 0, null, 0 };
+ Object[] values = new Object[]{
+ -1L, "", 0L, 0L, Calls.INCOMING_TYPE, "", "", "", null, 0, null,
+ CallLogQuery.SECTION_OLD_ITEM
+ };
Assert.assertEquals(CallLogQuery.EXTENDED_PROJECTION.length, values.length);
return values;
}
diff --git a/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java b/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java
index cfab94a..02faa24 100644
--- a/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java
+++ b/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java
@@ -43,7 +43,7 @@
values.put(Im.DATA, TEST_ADDRESS);
DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
- ContactDetailFragment.buildImActions(entry, values);
+ ContactDetailFragment.buildImActions(mContext, entry, values);
assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
assertEquals("xmpp:" + TEST_ADDRESS + "?message", entry.intent.getData().toString());
@@ -60,7 +60,7 @@
values.put(Im.CHAT_CAPABILITY, Im.CAPABILITY_HAS_VOICE | Im.CAPABILITY_HAS_VIDEO);
DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
- ContactDetailFragment.buildImActions(entry, values);
+ ContactDetailFragment.buildImActions(mContext, entry, values);
assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
assertEquals("xmpp:" + TEST_ADDRESS + "?message", entry.intent.getData().toString());
@@ -79,7 +79,7 @@
Im.CAPABILITY_HAS_VOICE);
DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
- ContactDetailFragment.buildImActions(entry, values);
+ ContactDetailFragment.buildImActions(mContext, entry, values);
assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
assertEquals("xmpp:" + TEST_ADDRESS + "?message", entry.intent.getData().toString());
@@ -98,7 +98,7 @@
values.put(Im.DATA, TEST_ADDRESS);
DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
- ContactDetailFragment.buildImActions(entry, values);
+ ContactDetailFragment.buildImActions(mContext, entry, values);
assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
final Uri data = entry.intent.getData();
@@ -121,7 +121,7 @@
Im.CAPABILITY_HAS_VOICE);
DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
- ContactDetailFragment.buildImActions(entry, values);
+ ContactDetailFragment.buildImActions(mContext, entry, values);
assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
assertEquals("xmpp:" + TEST_ADDRESS + "?message", entry.intent.getData().toString());
diff --git a/tests/src/com/android/contacts/util/FakeAsyncTaskExecutor.java b/tests/src/com/android/contacts/util/FakeAsyncTaskExecutor.java
new file mode 100644
index 0000000..e27c6fb
--- /dev/null
+++ b/tests/src/com/android/contacts/util/FakeAsyncTaskExecutor.java
@@ -0,0 +1,212 @@
+/*
+ * 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.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.Executors;
+
+import android.app.Instrumentation;
+import android.os.AsyncTask;
+
+import junit.framework.Assert;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Test implementation of AsyncTaskExecutor.
+ * <p>
+ * This class is thread-safe. As per the contract of the AsyncTaskExecutor, the submit methods must
+ * be called from the main ui thread, however the other public methods may be called from any thread
+ * (most commonly the test thread).
+ * <p>
+ * Tasks submitted to this executor will not be run immediately. Rather they will be stored in a
+ * list of submitted tasks, where they can be examined. They can also be run on-demand using the run
+ * methods, so that different ordering of AsyncTask execution can be simulated.
+ */
+@ThreadSafe
+public class FakeAsyncTaskExecutor implements AsyncTaskExecutor {
+ private static final long DEFAULT_TIMEOUT_MS = 10000;
+ private static final Executor DEFAULT_EXECUTOR = Executors.sameThreadExecutor();
+
+ /** The maximum length of time in ms to wait for tasks to execute during tests. */
+ private final long mTimeoutMs = DEFAULT_TIMEOUT_MS;
+ /** The executor for the background part of our test tasks. */
+ private final Executor mExecutor = DEFAULT_EXECUTOR;
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock") private final List<SubmittedTask<?>> mSubmittedTasks = Lists.newArrayList();
+
+ private final Instrumentation mInstrumentation;
+
+ /** Create a fake AsyncTaskExecutor for use in unit tests. */
+ public FakeAsyncTaskExecutor(Instrumentation instrumentation) {
+ mInstrumentation = Preconditions.checkNotNull(instrumentation);
+ }
+
+ /** Encapsulates an async task with the params and identifier it was submitted with. */
+ public interface SubmittedTask<T> {
+ AsyncTask<T, ?, ?> getTask();
+ T[] getParams();
+ Object getIdentifier();
+ }
+
+ private static final class SubmittedTaskImpl<T> implements SubmittedTask<T> {
+ private final Object mIdentifier;
+ private final AsyncTask<T, ?, ?> mTask;
+ private final T[] mParams;
+
+ public SubmittedTaskImpl(Object identifier, AsyncTask<T, ?, ?> task, T[] params) {
+ mIdentifier = identifier;
+ mTask = task;
+ mParams = params;
+ }
+
+ @Override
+ public Object getIdentifier() {
+ return mIdentifier;
+ }
+
+ @Override
+ public AsyncTask<T, ?, ?> getTask() {
+ return mTask;
+ }
+
+ @Override
+ public T[] getParams() {
+ return mParams;
+ }
+
+ @Override
+ public String toString() {
+ return "SubmittedTaskImpl [mIdentifier=" + mIdentifier + "]";
+ }
+ }
+
+ @Override
+ public <T> AsyncTask<T, ?, ?> submit(Object identifier, AsyncTask<T, ?, ?> task, T... params) {
+ AsyncTaskExecutors.checkCalledFromUiThread();
+ synchronized (mLock) {
+ mSubmittedTasks.add(new SubmittedTaskImpl<T>(identifier, task, params));
+ return task;
+ }
+ }
+
+ /**
+ * Runs a single task matching the given identifier.
+ * <p>
+ * Removes the matching task from the list of submitted tasks, then runs it. The executor used
+ * to execute this async task will be a same-thread executor.
+ * <p>
+ * Fails if there was not exactly one task matching the given identifier.
+ * <p>
+ * This method blocks until the AsyncTask has completely finished executing.
+ */
+ public void runTask(Enum<?> identifier) throws InterruptedException {
+ List<SubmittedTask<?>> tasks = getSubmittedTasksByIdentifier(identifier, true);
+ Assert.assertEquals("Expected one task " + identifier + ", got " + tasks, 1, tasks.size());
+ runTask(tasks.get(0));
+ }
+
+ /**
+ * Runs all tasks whose identifier matches the given identifier.
+ * <p>
+ * Removes all matching tasks from the list of submitted tasks, and runs them. The executor used
+ * to execute these async tasks will be a same-thread executor.
+ * <p>
+ * Fails if there were no tasks matching the given identifier.
+ * <p>
+ * This method blocks until the AsyncTask objects have completely finished executing.
+ */
+ public void runAllTasks(Enum<?> identifier) throws InterruptedException {
+ List<SubmittedTask<?>> tasks = getSubmittedTasksByIdentifier(identifier, true);
+ Assert.assertTrue("There were no tasks with identifier " + identifier, tasks.size() > 0);
+ for (SubmittedTask<?> task : tasks) {
+ runTask(task);
+ }
+ }
+
+ /**
+ * Executes a single {@link AsyncTask} using the supplied executors.
+ * <p>
+ * Blocks until the task has completed running.
+ */
+ private <T> void runTask(SubmittedTask<T> submittedTask) throws InterruptedException {
+ final AsyncTask<T, ?, ?> task = submittedTask.getTask();
+ task.executeOnExecutor(mExecutor, submittedTask.getParams());
+ // Block until the task has finished running in the background.
+ try {
+ task.get(mTimeoutMs, TimeUnit.MILLISECONDS);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e.getCause());
+ } catch (TimeoutException e) {
+ throw new RuntimeException("waited too long");
+ }
+ // Block until the onPostExecute or onCancelled has finished.
+ // Unfortunately we can't be sure when the AsyncTask will have posted its result handling
+ // code to the main ui thread, the best we can do is wait for the Status to be FINISHED.
+ final CountDownLatch latch = new CountDownLatch(1);
+ class AsyncTaskHasFinishedRunnable implements Runnable {
+ @Override
+ public void run() {
+ if (task.getStatus() == AsyncTask.Status.FINISHED) {
+ latch.countDown();
+ } else {
+ mInstrumentation.waitForIdle(this);
+ }
+ }
+ }
+ mInstrumentation.waitForIdle(new AsyncTaskHasFinishedRunnable());
+ Assert.assertTrue(latch.await(mTimeoutMs, TimeUnit.MILLISECONDS));
+ }
+
+ private List<SubmittedTask<?>> getSubmittedTasksByIdentifier(
+ Enum<?> identifier, boolean remove) {
+ Preconditions.checkNotNull(identifier, "can't lookup tasks by 'null' identifier");
+ List<SubmittedTask<?>> results = Lists.newArrayList();
+ synchronized (mLock) {
+ Iterator<SubmittedTask<?>> iter = mSubmittedTasks.iterator();
+ while (iter.hasNext()) {
+ SubmittedTask<?> task = iter.next();
+ if (identifier.equals(task.getIdentifier())) {
+ results.add(task);
+ iter.remove();
+ }
+ }
+ }
+ return results;
+ }
+
+ /** Get a factory that will return this instance - useful for testing. */
+ public AsyncTaskExecutors.AsyncTaskExecutorFactory getFactory() {
+ return new AsyncTaskExecutors.AsyncTaskExecutorFactory() {
+ @Override
+ public AsyncTaskExecutor createAsyncTaskExeuctor() {
+ return FakeAsyncTaskExecutor.this;
+ }
+ };
+ }
+}
diff --git a/tests/src/com/android/contacts/util/FakeBackgroundTaskService.java b/tests/src/com/android/contacts/util/FakeBackgroundTaskService.java
deleted file mode 100644
index 26be519..0000000
--- a/tests/src/com/android/contacts/util/FakeBackgroundTaskService.java
+++ /dev/null
@@ -1,86 +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.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);
- }
-
- @Override
- public void submit(BackgroundTask task, Executor executor) {
- 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();
- }
- }
- });
- }
- });
- }
- }
-}