Merge "Call log duration display for calls less than a minute." into mnc-dev
diff --git a/res/drawable-hdpi/ic_card_phone.png b/res/drawable-hdpi/ic_card_phone.png
deleted file mode 100644
index 5b29a32..0000000
--- a/res/drawable-hdpi/ic_card_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_delete_wht_24dp.png b/res/drawable-hdpi/ic_delete_24dp.png
similarity index 100%
rename from res/drawable-hdpi/ic_delete_wht_24dp.png
rename to res/drawable-hdpi/ic_delete_24dp.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_card_phone.png b/res/drawable-mdpi/ic_card_phone.png
deleted file mode 100644
index fb9ab44..0000000
--- a/res/drawable-mdpi/ic_card_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_delete_wht_24dp.png b/res/drawable-mdpi/ic_delete_24dp.png
similarity index 100%
rename from res/drawable-mdpi/ic_delete_wht_24dp.png
rename to res/drawable-mdpi/ic_delete_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_card_phone.png b/res/drawable-xhdpi/ic_card_phone.png
deleted file mode 100644
index 77df721..0000000
--- a/res/drawable-xhdpi/ic_card_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_delete_wht_24dp.png b/res/drawable-xhdpi/ic_delete_24dp.png
similarity index 100%
rename from res/drawable-xhdpi/ic_delete_wht_24dp.png
rename to res/drawable-xhdpi/ic_delete_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_card_phone.png b/res/drawable-xxhdpi/ic_card_phone.png
deleted file mode 100644
index d7e52ed..0000000
--- a/res/drawable-xxhdpi/ic_card_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_delete_wht_24dp.png b/res/drawable-xxhdpi/ic_delete_24dp.png
similarity index 100%
rename from res/drawable-xxhdpi/ic_delete_wht_24dp.png
rename to res/drawable-xxhdpi/ic_delete_24dp.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_card_phone.png b/res/drawable-xxxhdpi/ic_card_phone.png
deleted file mode 100644
index a938659..0000000
--- a/res/drawable-xxxhdpi/ic_card_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_delete_wht_24dp.png b/res/drawable-xxxhdpi/ic_delete_24dp.png
similarity index 100%
rename from res/drawable-xxxhdpi/ic_delete_wht_24dp.png
rename to res/drawable-xxxhdpi/ic_delete_24dp.png
Binary files differ
diff --git a/res/drawable/ic_calllog_delete.xml b/res/drawable/ic_calllog_delete.xml
deleted file mode 100644
index 2f9f0ae..0000000
--- a/res/drawable/ic_calllog_delete.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 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.
--->
-
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_delete_wht_24dp"
-    android:gravity="center"
-    android:tint="@color/actionbar_icon_color" />
diff --git a/res/layout/call_detail.xml b/res/layout/call_detail.xml
index c077851..abc0fbb 100644
--- a/res/layout/call_detail.xml
+++ b/res/layout/call_detail.xml
@@ -13,91 +13,98 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/call_detail"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:layout_alignParentStart="true"
+    android:layout_alignParentTop="true"
+    android:background="@color/background_dialer_call_log" >
 
+    <!-- Caller information "card" -->
     <LinearLayout
-        android:id="@+id/call_detail"
+        android:id="@+id/caller_information"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:layout_alignParentStart="true"
-        android:layout_alignParentTop="true"
-        android:background="@color/background_dialer_call_log" >
-        <!-- Caller information "card" -->
+        android:layout_height="wrap_content"
+        android:paddingStart="@dimen/call_detail_horizontal_margin"
+        android:paddingTop="@dimen/call_detail_top_margin"
+        android:paddingBottom="@dimen/call_detail_bottom_margin"
+        android:baselineAligned="false"
+        android:orientation="horizontal"
+        android:translationZ="@dimen/call_detail_translation_z"
+        android:focusable="true"
+        android:background="@color/background_dialer_white" >
+
+        <QuickContactBadge
+            android:id="@+id/quick_contact_photo"
+            android:layout_width="@dimen/contact_photo_size"
+            android:layout_height="@dimen/contact_photo_size"
+            android:layout_alignParentStart="true"
+            android:layout_gravity="top"
+            android:layout_marginTop="3dp"
+            android:focusable="true" />
+
         <LinearLayout
-            android:id="@+id/caller_information"
-            android:layout_width="match_parent"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:paddingStart="@dimen/call_detail_horizontal_margin"
-            android:paddingTop="@dimen/call_detail_top_margin"
-            android:paddingBottom="@dimen/call_detail_bottom_margin"
-            android:baselineAligned="false"
-            android:orientation="horizontal"
-            android:translationZ="@dimen/call_detail_translation_z"
-            android:focusable="true"
-            android:background="@color/background_dialer_white" >
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:gravity="center_vertical"
+            android:layout_marginStart="@dimen/call_detail_horizontal_margin">
 
-            <QuickContactBadge
-                android:id="@+id/quick_contact_photo"
-                android:layout_width="@dimen/contact_photo_size"
-                android:layout_height="@dimen/contact_photo_size"
-                android:layout_alignParentStart="true"
-                android:layout_gravity="top"
-                android:layout_marginTop="3dp"
-                android:focusable="true"
-                />
-
-            <LinearLayout
+            <TextView
+                android:id="@+id/caller_name"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:orientation="vertical"
-                android:gravity="center_vertical"
-                android:layout_marginStart="@dimen/call_detail_horizontal_margin"
-                >
-                <TextView
-                    android:id="@+id/caller_name"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:textColor="?attr/call_log_primary_text_color"
-                    android:textSize="@dimen/call_log_primary_text_size"
-                    android:includeFontPadding="false"
-                    android:layout_marginBottom="5dp"
-                    android:singleLine="true"
-                    />
-                <TextView
-                    android:id="@+id/caller_number"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:textColor="?attr/call_log_secondary_text_color"
-                    android:textSize="@dimen/call_log_secondary_text_size"
-                    android:layout_marginBottom="1dp"
-                    android:singleLine="true"
-                    />
-                <TextView
-                    android:id="@+id/phone_account_label"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:textColor="?attr/call_log_secondary_text_color"
-                    android:textSize="@dimen/call_log_secondary_text_size"
-                    android:singleLine="true"
-                    android:visibility="gone"
-                    />
-            </LinearLayout>
+                android:textColor="?attr/call_log_primary_text_color"
+                android:textSize="@dimen/call_log_primary_text_size"
+                android:includeFontPadding="false"
+                android:layout_marginBottom="5dp"
+                android:singleLine="true" />
+
+            <TextView
+                android:id="@+id/caller_number"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/call_log_secondary_text_color"
+                android:textSize="@dimen/call_log_secondary_text_size"
+                android:layout_marginBottom="1dp"
+                android:singleLine="true" />
+
+            <TextView
+                android:id="@+id/phone_account_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/call_log_secondary_text_color"
+                android:textSize="@dimen/call_log_secondary_text_size"
+                android:singleLine="true"
+                android:visibility="gone" />
+
         </LinearLayout>
 
-        <!--
-          The list view is under everything.
-          It contains a first header element which is hidden under the controls UI.
-          When scrolling, the controls move up until the name bar hits the top.
-          -->
-        <ListView
-            android:id="@+id/history"
-            android:layout_width="match_parent"
-            android:layout_height="fill_parent"
-        />
-
+        <ImageView
+            android:id="@+id/call_back_button"
+            android:layout_width="@dimen/call_button_dimen"
+            android:layout_height="@dimen/call_button_dimen"
+            android:layout_marginEnd="16dp"
+            android:background="?android:attr/selectableItemBackgroundBorderless"
+            android:src="@drawable/ic_call_24dp"
+            android:scaleType="center"
+            android:tint="@color/recent_call_log_item_phone_icon_tint"
+            android:contentDescription="@string/description_call_log_call_action"
+            android:visibility="gone" />
 
     </LinearLayout>
-</RelativeLayout>
+
+    <!--
+      The list view is under everything.
+      It contains a first header element which is hidden under the controls UI.
+      When scrolling, the controls move up until the name bar hits the top.
+      -->
+    <ListView
+        android:id="@+id/history"
+        android:layout_width="match_parent"
+        android:layout_height="fill_parent" />
+
+</LinearLayout>
diff --git a/res/layout/call_log_list_item.xml b/res/layout/call_log_list_item.xml
index af8cb8b..7fce595 100644
--- a/res/layout/call_log_list_item.xml
+++ b/res/layout/call_log_list_item.xml
@@ -138,14 +138,14 @@
 
                 <ImageView
                     android:id="@+id/call_icon"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
+                    android:layout_width="@dimen/call_button_dimen"
+                    android:layout_height="@dimen/call_button_dimen"
                     android:layout_gravity="center_vertical"
                     android:layout_marginEnd="@dimen/call_log_icon_margin"
                     android:background="?android:attr/selectableItemBackgroundBorderless"
-                    android:src="@drawable/ic_card_phone"
+                    android:src="@drawable/ic_call_24dp"
+                    android:scaleType="center"
                     android:tint="@color/recent_call_log_item_phone_icon_tint"
-                    android:alpha="0.3"
                     android:contentDescription="@string/description_call_log_call_action"
                     android:visibility="gone" />
 
diff --git a/res/layout/call_log_list_item_actions.xml b/res/layout/call_log_list_item_actions.xml
index f1d0e9e..569048b 100644
--- a/res/layout/call_log_list_item_actions.xml
+++ b/res/layout/call_log_list_item_actions.xml
@@ -66,7 +66,7 @@
 
         <TextView
             style="@style/CallLogActionTextStyle"
-            android:text="@string/search_shortcut_add_to_existing_contact" />
+            android:text="@string/search_shortcut_add_to_contact" />
 
     </LinearLayout>
 
diff --git a/res/layout/voicemail_playback_layout.xml b/res/layout/voicemail_playback_layout.xml
index 96feba6..97bb5f8 100644
--- a/res/layout/voicemail_playback_layout.xml
+++ b/res/layout/voicemail_playback_layout.xml
@@ -27,11 +27,6 @@
         android:layout_height="80dp"
         android:layout_marginTop="@dimen/call_detail_button_spacing">
 
-        <!-- SeekBar left-right margin decreased from redlines 72dp by 8dp to account for
-             half thumb width (thumb is 16dp).
-             Vertically, SeekBar and rate buttons should be below centre, position achieved by
-             making them centred but giving a difference between top and bottom padding,
-             difference is currently 10dp. -->
         <SeekBar
             android:id="@+id/playback_seek"
             android:layout_width="match_parent"
@@ -61,15 +56,12 @@
 
     </RelativeLayout>
 
-    <!-- Playback, speakerphone buttons. -->
-    <LinearLayout
-        android:id="@+id/buttons_linear_layout"
+    <LinearLayout android:id="@+id/buttons_linear_layout"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal">
 
-        <ImageButton
-            android:id="@+id/playback_start_stop"
+        <ImageButton android:id="@+id/playback_start_stop"
             android:layout_width="match_parent"
             android:layout_height="58dp"
             android:layout_marginEnd="@dimen/call_detail_button_spacing"
@@ -78,8 +70,7 @@
             android:src="@drawable/ic_hold_pause"
             android:contentDescription="@string/voicemail_play_start_pause" />
 
-        <ImageButton
-            android:id="@+id/playback_speakerphone"
+        <ImageButton android:id="@+id/playback_speakerphone"
             android:layout_width="match_parent"
             android:layout_height="58dp"
             android:layout_weight="1"
@@ -87,6 +78,15 @@
             android:src="@drawable/ic_speakerphone_on"
             android:contentDescription="@string/description_playback_speakerphone" />
 
+        <ImageButton android:id="@+id/delete_voicemail"
+            android:layout_width="match_parent"
+            android:layout_height="58dp"
+            android:layout_weight="1"
+            android:background="?android:attr/selectableItemBackground"
+            android:src="@drawable/ic_delete_24dp"
+            android:tint="@color/voicemail_playback_icon_tint"
+            android:contentDescription="@string/recentCalls_trashVoicemail" />
+
     </LinearLayout>
 
 </LinearLayout>
diff --git a/res/menu/call_details_options.xml b/res/menu/call_details_options.xml
index a48687e..b98ec9e 100644
--- a/res/menu/call_details_options.xml
+++ b/res/menu/call_details_options.xml
@@ -14,23 +14,21 @@
      limitations under the License.
 -->
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:id="@+id/menu_trash"
-        android:icon="@drawable/ic_calllog_delete"
+
+    <item android:id="@+id/menu_trash"
+        android:icon="@drawable/ic_delete_24dp"
         android:showAsAction="ifRoom"
         android:title="@string/recentCalls_trashVoicemail"
-        android:onClick="onMenuTrashVoicemail"
-    />
-    <item
-        android:id="@+id/menu_remove_from_call_log"
-        android:icon="@drawable/ic_calllog_delete"
+        android:onClick="onMenuTrashVoicemail" />
+
+    <item android:id="@+id/menu_remove_from_call_log"
+        android:icon="@drawable/ic_delete_24dp"
         android:showAsAction="ifRoom"
         android:title="@string/recentCalls_removeFromRecentList"
-        android:onClick="onMenuRemoveFromCallLog"
-    />
-    <item
-        android:id="@+id/menu_edit_number_before_call"
+        android:onClick="onMenuRemoveFromCallLog" />
+
+    <item android:id="@+id/menu_edit_number_before_call"
         android:title="@string/recentCalls_editNumberBeforeCall"
-        android:onClick="onMenuEditNumberBeforeCall"
-    />
+        android:onClick="onMenuEditNumberBeforeCall" />
+
 </menu>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 86ac9be..352bce5 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -36,8 +36,10 @@
     <!-- Color of the text describing an unconsumed voicemail. -->
     <color name="call_log_voicemail_highlight_color">#33b5e5</color>
 
-    <!-- Tint of the recent card phone icon -->
-    <color name="recent_call_log_item_phone_icon_tint">#000000</color>
+    <!-- Tint of the recent card phone icon; 30% black -->
+    <color name="recent_call_log_item_phone_icon_tint">#4d000000</color>
+
+    <color name="voicemail_playback_icon_tint">#8e8e8e</color>
 
     <!--
          Colour of voicemail progress bar to the right of position indicator.
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index b36b9d6..9d987e8 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -132,4 +132,6 @@
     <dimen name="preference_padding_bottom">16dp</dimen>
     <dimen name="preference_side_margin">16dp</dimen>
     <dimen name="preference_summary_line_spacing_extra">4dp</dimen>
+
+    <dimen name="call_button_dimen">36dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c661e58..b852be8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -51,7 +51,7 @@
     <string name="recentCalls_editNumberBeforeCall">Edit number before call</string>
 
     <!-- Menu item used to add a number from the call log to contacts -->
-    <string name="recentCalls_addToContact">Add to contacts</string>
+    <string name="recentCalls_addToContact">Add to a contact</string>
 
     <!-- Menu item used to remove a single call from the call log -->
     <string name="recentCalls_removeFromRecentList">Delete from call history</string>
@@ -503,7 +503,7 @@
 
     <!-- Shortcut item used to add a number to an existing contact directly from search.
          [CHAR LIMIT=25] -->
-    <string name="search_shortcut_add_to_contact">Add to contact</string>
+    <string name="search_shortcut_add_to_contact">Add to a contact</string>
 
     <!-- Shortcut item used to send a text message directly from search. [CHAR LIMIT=25] -->
     <string name="search_shortcut_send_sms_message">Send SMS</string>
diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java
index 6da7c79..15ba5df 100644
--- a/src/com/android/dialer/CallDetailActivity.java
+++ b/src/com/android/dialer/CallDetailActivity.java
@@ -77,8 +77,6 @@
     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. */
     public static final String EXTRA_VOICEMAIL_URI = "EXTRA_VOICEMAIL_URI";
-    /** If we should immediately start playback of the voicemail, this extra will be set to true. */
-    public static final String EXTRA_VOICEMAIL_START_PLAYBACK = "EXTRA_VOICEMAIL_START_PLAYBACK";
     /** If the activity was triggered from a notification. */
     public static final String EXTRA_FROM_NOTIFICATION = "EXTRA_FROM_NOTIFICATION";
 
@@ -97,11 +95,9 @@
 
         @Override
         public void onGetCallDetails(PhoneCallDetails[] details) {
-            Context context = CallDetailActivity.this;
-
             if (details == null) {
                 // Somewhere went wrong: we're going to bail out and show error to users.
-                Toast.makeText(context, R.string.toast_call_detail_error,
+                Toast.makeText(mContext, R.string.toast_call_detail_error,
                         Toast.LENGTH_SHORT).show();
                 finish();
                 return;
@@ -119,9 +115,9 @@
 
             // Cache the details about the phone number.
             final boolean canPlaceCallsTo =
-                PhoneNumberUtilsWrapper.canPlaceCallsTo(mNumber, numberPresentation);
-            final PhoneNumberUtilsWrapper phoneUtils = new PhoneNumberUtilsWrapper(context);
-            final boolean isVoicemailNumber =
+                    PhoneNumberUtilsWrapper.canPlaceCallsTo(mNumber, numberPresentation);
+            final PhoneNumberUtilsWrapper phoneUtils = new PhoneNumberUtilsWrapper(mContext);
+            mIsVoicemailNumber =
                     phoneUtils.isVoicemailNumber(accountHandle, mNumber);
             final boolean isSipNumber = PhoneNumberUtilsWrapper.isSipNumber(mNumber);
 
@@ -144,7 +140,9 @@
                 }
             }
 
-            String accountLabel = PhoneAccountUtils.getAccountLabel(context, accountHandle);
+            mCallButton.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE);
+
+            String accountLabel = PhoneAccountUtils.getAccountLabel(mContext, accountHandle);
             if (!TextUtils.isEmpty(accountLabel)) {
                 mAccountLabel.setText(accountLabel);
                 mAccountLabel.setVisibility(View.VISIBLE);
@@ -153,14 +151,14 @@
             }
 
             mHasEditNumberBeforeCallOption =
-                    canPlaceCallsTo && !isSipNumber && !isVoicemailNumber;
+                    canPlaceCallsTo && !isSipNumber && !mIsVoicemailNumber;
             mHasTrashOption = hasVoicemail();
             mHasRemoveFromCallLogOption = !hasVoicemail();
             invalidateOptionsMenu();
 
             ListView historyList = (ListView) findViewById(R.id.history);
             historyList.setAdapter(
-                    new CallDetailHistoryAdapter(context, mInflater, mCallTypeHelper, details));
+                    new CallDetailHistoryAdapter(mContext, mInflater, mCallTypeHelper, details));
 
             String lookupKey = contactUri == null ? null
                     : ContactInfoHelper.getLookupKeyFromUri(contactUri);
@@ -168,7 +166,7 @@
             final boolean isBusiness = mContactInfoHelper.isBusiness(firstDetails.sourceType);
 
             final int contactType =
-                    isVoicemailNumber ? ContactPhotoManager.TYPE_VOICEMAIL :
+                    mIsVoicemailNumber ? ContactPhotoManager.TYPE_VOICEMAIL :
                     isBusiness ? ContactPhotoManager.TYPE_BUSINESS :
                     ContactPhotoManager.TYPE_DEFAULT;
 
@@ -201,14 +199,17 @@
         }
     };
 
+    private Context mContext;
     private CallTypeHelper mCallTypeHelper;
     private QuickContactBadge mQuickContactBadge;
     private TextView mCallerName;
     private TextView mCallerNumber;
     private TextView mAccountLabel;
+    private View mCallButton;
     private ContactInfoHelper mContactInfoHelper;
 
-    private String mNumber = null;
+    private String mNumber;
+    private boolean mIsVoicemailNumber;
     private String mDefaultCountryIso;
 
     /* package */ LayoutInflater mInflater;
@@ -230,6 +231,8 @@
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
+        mContext = this;
+
         setContentView(R.layout.call_detail);
 
         mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
@@ -248,6 +251,14 @@
         mDefaultCountryIso = GeoUtil.getCurrentCountryIso(this);
         mContactPhotoManager = ContactPhotoManager.getInstance(this);
 
+        mCallButton = (View) findViewById(R.id.call_back_button);
+        mCallButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mContext.startActivity(IntentUtil.getCallIntent(mNumber));
+            }
+        });
+
         mContactInfoHelper = new ContactInfoHelper(this, GeoUtil.getCurrentCountryIso(this));
         getActionBar().setDisplayHomeAsUpEnabled(true);
 
diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java
index 845f911..d57d87a 100644
--- a/src/com/android/dialer/calllog/CallLogFragment.java
+++ b/src/com/android/dialer/calllog/CallLogFragment.java
@@ -191,6 +191,7 @@
         setHasOptionsMenu(true);
 
         mVoicemailPlaybackPresenter = new VoicemailPlaybackPresenter(activity, state);
+        activity.setVolumeControlStream(VoicemailPlaybackPresenter.PLAYBACK_STREAM);
     }
 
     /** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */
diff --git a/src/com/android/dialer/calllog/GroupingListAdapter.java b/src/com/android/dialer/calllog/GroupingListAdapter.java
index 501e88d..8d3ab45 100644
--- a/src/com/android/dialer/calllog/GroupingListAdapter.java
+++ b/src/com/android/dialer/calllog/GroupingListAdapter.java
@@ -253,7 +253,6 @@
      * corresponding cursor position.
      */
     public void obtainPositionMetadata(PositionMetadata metadata, int position) {
-
         // If the description object already contains requested information, just return
         if (metadata.listPosition == position) {
             return;
@@ -433,17 +432,4 @@
             return -1;
         }
     }
-
-    /**
-     * Used for setting the cursor without triggering a UI thread update.
-     */
-    @NeededForTesting
-    public void setCursorForTesting(Cursor cursor) {
-        if (cursor != null) {
-            mCursor = cursor;
-            cursor.registerContentObserver(mChangeObserver);
-            cursor.registerDataSetObserver(mDataSetObserver);
-            mRowIdColumnIndex = cursor.getColumnIndexOrThrow("_id");
-        }
-    }
 }
diff --git a/src/com/android/dialer/calllog/IntentProvider.java b/src/com/android/dialer/calllog/IntentProvider.java
index d7a35e8..a11d00b 100644
--- a/src/com/android/dialer/calllog/IntentProvider.java
+++ b/src/com/android/dialer/calllog/IntentProvider.java
@@ -83,24 +83,6 @@
         };
     }
 
-    public static IntentProvider getPlayVoicemailIntentProvider(final long rowId,
-            final String voicemailUri) {
-        return new IntentProvider() {
-            @Override
-            public Intent getIntent(Context context) {
-                Intent intent = new Intent(context, CallDetailActivity.class);
-                intent.setData(ContentUris.withAppendedId(
-                        Calls.CONTENT_URI_WITH_VOICEMAIL, rowId));
-                if (voicemailUri != null) {
-                    intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI,
-                            Uri.parse(voicemailUri));
-                }
-                intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK, true);
-                return intent;
-            }
-        };
-    }
-
     public static IntentProvider getSendSmsIntentProvider(final String number) {
         return new IntentProvider() {
             @Override
@@ -129,7 +111,6 @@
                     intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI,
                             Uri.parse(voicemailUri));
                 }
-                intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK, false);
 
                 if (extraIds != null && extraIds.length > 0) {
                     intent.putExtra(CallDetailActivity.EXTRA_CALL_LOG_IDS, extraIds);
diff --git a/src/com/android/dialer/list/AllContactsFragment.java b/src/com/android/dialer/list/AllContactsFragment.java
index eaa5cc8..71c6980 100644
--- a/src/com/android/dialer/list/AllContactsFragment.java
+++ b/src/com/android/dialer/list/AllContactsFragment.java
@@ -16,6 +16,7 @@
 
 package com.android.dialer.list;
 
+import android.content.Loader;
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
@@ -39,6 +40,8 @@
  */
 public class AllContactsFragment extends ContactEntryListFragment<ContactEntryListAdapter> {
 
+    private View mEmptyListView;
+
     public AllContactsFragment() {
         setQuickContactEnabled(false);
         setAdjustSelectionBoundsEnabled(true);
@@ -52,10 +55,11 @@
     public void onViewCreated(View view, android.os.Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
 
-        View emptyListView = view.findViewById(R.id.empty_list_view);
-        DialerUtils.configureEmptyListView(emptyListView, R.drawable.empty_contacts,
+        mEmptyListView = view.findViewById(R.id.empty_list_view);
+        DialerUtils.configureEmptyListView(mEmptyListView, R.drawable.empty_contacts,
                 R.string.all_contacts_empty, getResources());
-        getListView().setEmptyView(emptyListView);
+        getListView().setEmptyView(mEmptyListView);
+        mEmptyListView.setVisibility(View.GONE);
 
         ViewUtil.addBottomPaddingToListViewForFab(getListView(), getResources());
     }
@@ -68,6 +72,15 @@
     }
 
     @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        super.onLoadFinished(loader, data);
+
+        if (data.getCount() == 0) {
+            mEmptyListView.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
     protected ContactEntryListAdapter createListAdapter() {
         if (!PermissionsUtil.hasContactsPermissions(getActivity())) {
             return new EmptyContactsListAdapter(getActivity());
diff --git a/src/com/android/dialer/list/DialerPhoneNumberListAdapter.java b/src/com/android/dialer/list/DialerPhoneNumberListAdapter.java
index 8a43023..17c573f 100644
--- a/src/com/android/dialer/list/DialerPhoneNumberListAdapter.java
+++ b/src/com/android/dialer/list/DialerPhoneNumberListAdapter.java
@@ -3,6 +3,8 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.telephony.PhoneNumberUtils;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -36,6 +38,8 @@
 
     private final boolean[] mShortcutEnabled = new boolean[SHORTCUT_COUNT];
 
+    private final BidiFormatter mBidiFormatter = BidiFormatter.getInstance();
+
     public DialerPhoneNumberListAdapter(Context context) {
         super(context);
 
@@ -141,7 +145,9 @@
         final String number = getFormattedQueryString();
         switch (shortcutType) {
             case SHORTCUT_DIRECT_CALL:
-                text = resources.getString(R.string.search_shortcut_call_number, number);
+                text = resources.getString(
+                        R.string.search_shortcut_call_number,
+                        mBidiFormatter.unicodeWrap(number, TextDirectionHeuristics.LTR));
                 drawableId = R.drawable.ic_search_phone;
                 break;
             case SHORTCUT_CREATE_NEW_CONTACT:
diff --git a/src/com/android/dialer/settings/SoundSettingsFragment.java b/src/com/android/dialer/settings/SoundSettingsFragment.java
index 43297b5..c7bb2d3 100644
--- a/src/com/android/dialer/settings/SoundSettingsFragment.java
+++ b/src/com/android/dialer/settings/SoundSettingsFragment.java
@@ -28,6 +28,7 @@
 import android.preference.PreferenceFragment;
 import android.preference.PreferenceScreen;
 import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
 import android.telephony.TelephonyManager;
 
 import com.android.dialer.R;
@@ -208,8 +209,9 @@
     }
 
     private boolean shouldHideCarrierSettings() {
-        int hideCarrierNetworkSetting = Settings.Global.getInt(getActivity().getContentResolver(),
-                Settings.Global.HIDE_CARRIER_NETWORK_SETTINGS, SHOW_CARRIER_SETTINGS);
-        return hideCarrierNetworkSetting == HIDE_CARRIER_SETTINGS;
+        CarrierConfigManager configManager = (CarrierConfigManager) getActivity().getSystemService(
+                Context.CARRIER_CONFIG_SERVICE);
+        return configManager.getConfig().getBoolean(
+                CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL);
     }
 }
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java b/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
index 703004d..0313f40 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
@@ -37,6 +37,7 @@
 
 import com.android.common.io.MoreCloseables;
 import com.android.dialer.R;
+import com.android.dialer.calllog.CallLogAsyncTaskUtil;
 
 import com.google.common.base.Preconditions;
 
@@ -124,7 +125,7 @@
     /**
      * Handle state changes when the user manipulates the seek bar.
      */
-    private final OnSeekBarChangeListener seekBarChangeListener = new OnSeekBarChangeListener() {
+    private final OnSeekBarChangeListener mSeekBarChangeListener = new OnSeekBarChangeListener() {
         @Override
         public void onStartTrackingTouch(SeekBar seekBar) {
             if (mPresenter != null) {
@@ -148,7 +149,7 @@
     /**
      * Click listener to toggle speakerphone.
      */
-    private final View.OnClickListener speakerphoneListener = new View.OnClickListener() {
+    private final View.OnClickListener mSpeakerphoneListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
             if (mPresenter != null) {
@@ -160,7 +161,7 @@
     /**
      * Click listener to play or pause voicemail playback.
      */
-    private final View.OnClickListener startStopButtonListener = new View.OnClickListener() {
+    private final View.OnClickListener mStartStopButtonListener = new View.OnClickListener() {
         @Override
         public void onClick(View view) {
             if (mPresenter == null) {
@@ -175,6 +176,16 @@
         }
     };
 
+    private final View.OnClickListener mDeleteButtonListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View view ) {
+            if (mPresenter == null) {
+                return;
+            }
+            CallLogAsyncTaskUtil.deleteVoicemail(mContext, mPresenter.getVoicemailUri(), null);
+        }
+    };
+
     private Context mContext;
     private VoicemailPlaybackPresenter mPresenter;
 
@@ -183,6 +194,7 @@
     private SeekBar mPlaybackSeek;
     private ImageButton mStartStopButton;
     private ImageButton mPlaybackSpeakerphone;
+    private ImageButton mDeleteButton;
     private TextView mPlaybackPosition;
 
     private PositionUpdater mPositionUpdater;
@@ -212,11 +224,13 @@
         mPlaybackSeek = (SeekBar) findViewById(R.id.playback_seek);
         mStartStopButton = (ImageButton) findViewById(R.id.playback_start_stop);
         mPlaybackSpeakerphone = (ImageButton) findViewById(R.id.playback_speakerphone);
+        mDeleteButton = (ImageButton) findViewById(R.id.delete_voicemail);
         mPlaybackPosition = (TextView) findViewById(R.id.playback_position_text);
 
-        mPlaybackSeek.setOnSeekBarChangeListener(seekBarChangeListener);
-        mStartStopButton.setOnClickListener(startStopButtonListener);
-        mPlaybackSpeakerphone.setOnClickListener(speakerphoneListener);
+        mPlaybackSeek.setOnSeekBarChangeListener(mSeekBarChangeListener);
+        mStartStopButton.setOnClickListener(mStartStopButtonListener);
+        mPlaybackSpeakerphone.setOnClickListener(mSpeakerphoneListener);
+        mDeleteButton.setOnClickListener(mDeleteButtonListener);
     }
 
     @Override
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
index 99d2734..da2a29c 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
@@ -98,7 +98,7 @@
         VoicemailContract.Voicemails.HAS_CONTENT,
     };
 
-    private static final int PLAYBACK_STREAM = AudioManager.STREAM_VOICE_CALL;
+    public static final int PLAYBACK_STREAM = AudioManager.STREAM_VOICE_CALL;
     private static final int NUMBER_OF_THREADS_IN_POOL = 2;
     // Time to wait for content to be fetched before timing out.
     private static final long FETCH_CONTENT_TIMEOUT_MS = 20000;
@@ -141,11 +141,12 @@
      * This variable is thread-contained, accessed only on the ui thread.
      */
     private FetchResultHandler mFetchResultHandler;
+    private Handler mHandler = new Handler();
     private PowerManager.WakeLock mProximityWakeLock;
     private AudioManager mAudioManager;
 
-    public VoicemailPlaybackPresenter(Activity activity, Bundle savedInstanceState) {
-        mContext = activity;
+    public VoicemailPlaybackPresenter(Context context, Bundle savedInstanceState) {
+        mContext = context;
         mAsyncTaskExecutor = AsyncTaskExecutors.createAsyncTaskExecutor();
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
 
@@ -173,8 +174,6 @@
         mMediaPlayer.setOnPreparedListener(this);
         mMediaPlayer.setOnErrorListener(this);
         mMediaPlayer.setOnCompletionListener(this);
-
-        activity.setVolumeControlStream(PLAYBACK_STREAM);
     }
 
     /**
@@ -204,7 +203,7 @@
 
     public void onPause(boolean isFinishing) {
         // Do not pause for orientation changes.
-        if (mMediaPlayer.isPlaying() && isFinishing) {
+        if (mIsPrepared && mMediaPlayer.isPlaying() && isFinishing) {
             pausePlayback();
         }
 
@@ -298,13 +297,12 @@
      * was not available.
      */
     private void requestContent() {
-        Preconditions.checkState(mFetchResultHandler == null, "mFetchResultHandler should be null");
+        if (mFetchResultHandler != null) {
+            mFetchResultHandler.destroy();
+        }
 
-        Handler handler = new Handler();
-        mFetchResultHandler = new FetchResultHandler(handler);
-        mContext.getContentResolver().registerContentObserver(
-                mVoicemailUri, false, mFetchResultHandler);
-        handler.postDelayed(mFetchResultHandler.getTimeoutRunnable(), FETCH_CONTENT_TIMEOUT_MS);
+        mFetchResultHandler = new FetchResultHandler(new Handler());
+        mFetchResultHandler.registerContentObserver(mVoicemailUri);
 
         // Send voicemail fetch request.
         Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, mVoicemailUri);
@@ -313,30 +311,37 @@
 
     @ThreadSafe
     private class FetchResultHandler extends ContentObserver implements Runnable {
-        private AtomicBoolean mResultStillPending = new AtomicBoolean(true);
-        private final Handler mHandler;
+        private AtomicBoolean mIsWaitingForResult = new AtomicBoolean(true);
+        private final Handler mFetchResultHandler;
 
         public FetchResultHandler(Handler handler) {
             super(handler);
-            mHandler = handler;
+            mFetchResultHandler = handler;
         }
 
-        public Runnable getTimeoutRunnable() {
-            return this;
+        public void registerContentObserver(Uri voicemailUri) {
+            if (mIsWaitingForResult.get()) {
+                mContext.getContentResolver().registerContentObserver(
+                        voicemailUri, false, this);
+                mFetchResultHandler.postDelayed(this, FETCH_CONTENT_TIMEOUT_MS);
+            }
         }
 
+        /**
+         * Stop waiting for content and notify UI if {@link FETCH_CONTENT_TIMEOUT_MS} has elapsed.
+         */
         @Override
         public void run() {
-            if (mResultStillPending.getAndSet(false)) {
-                mContext.getContentResolver().unregisterContentObserver(FetchResultHandler.this);
+            if (mIsWaitingForResult.getAndSet(false)) {
+                mContext.getContentResolver().unregisterContentObserver(this);
                 mView.setFetchContentTimeout();
             }
         }
 
         public void destroy() {
-            if (mResultStillPending.getAndSet(false)) {
-                mContext.getContentResolver().unregisterContentObserver(FetchResultHandler.this);
-                mHandler.removeCallbacks(this);
+            if (mIsWaitingForResult.getAndSet(false)) {
+                mContext.getContentResolver().unregisterContentObserver(this);
+                mFetchResultHandler.removeCallbacks(this);
             }
         }
 
@@ -352,7 +357,7 @@
                 @Override
                 public void onPostExecute(Boolean hasContent) {
                     if (hasContent) {
-                        if (mResultStillPending.getAndSet(false)) {
+                        if (mIsWaitingForResult.getAndSet(false)) {
                             mContext.getContentResolver().unregisterContentObserver(
                                     FetchResultHandler.this);
                             prepareToPlayContent();
@@ -556,6 +561,10 @@
         return mAudioManager.isSpeakerphoneOn();
     }
 
+    public Uri getVoicemailUri() {
+        return mVoicemailUri;
+    }
+
     private static synchronized ScheduledExecutorService getScheduledExecutorServiceInstance() {
         if (mScheduledExecutorService == null) {
             mScheduledExecutorService = Executors.newScheduledThreadPool(NUMBER_OF_THREADS_IN_POOL);
@@ -563,4 +572,8 @@
         return mScheduledExecutorService;
     }
 
+    @VisibleForTesting
+    public boolean isPlaying() {
+        return mIsPlaying;
+    }
 }
diff --git a/tests/src/com/android/dialer/CallDetailActivityTest.java b/tests/src/com/android/dialer/CallDetailActivityTest.java
index 97b1b09..3b6b611 100644
--- a/tests/src/com/android/dialer/CallDetailActivityTest.java
+++ b/tests/src/com/android/dialer/CallDetailActivityTest.java
@@ -17,13 +17,11 @@
 package com.android.dialer;
 
 import static com.android.dialer.calllog.CallLogAsyncTaskUtil.Tasks.GET_CALL_DETAILS;
-import static com.android.dialer.voicemail.VoicemailPlaybackPresenter.Tasks.CHECK_FOR_CONTENT;
 
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Intent;
-import android.content.res.AssetManager;
 import android.net.Uri;
 import android.provider.CallLog;
 import android.provider.VoicemailContract;
@@ -36,31 +34,19 @@
 import com.android.dialer.calllog.CallLogAsyncTaskUtil;
 import com.android.dialer.util.AsyncTaskExecutors;
 import com.android.dialer.util.FakeAsyncTaskExecutor;
-import com.android.contacts.common.test.IntegrationTestUtils;
-import com.android.dialer.util.LocaleTestUtils;
 import com.android.internal.view.menu.ContextMenuBuilder;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.List;
-import java.util.Locale;
-
 /**
  * Unit tests for the {@link CallDetailActivity}. NOTE: The screen needs to be on for the
  * UI-related tests to pass.
  */
 @LargeTest
 public class CallDetailActivityTest extends ActivityInstrumentationTestCase2<CallDetailActivity> {
-    private static final String TEST_ASSET_NAME = "quick_test_recording.mp3";
-    private static final String MIME_TYPE = "audio/mp3";
     private static final String CONTACT_NUMBER = "+1412555555";
     private static final String VOICEMAIL_FILE_LOCATION = "/sdcard/sadlfj893w4j23o9sfu.mp3";
 
     private Uri mCallLogUri;
     private Uri mVoicemailUri;
-    private IntegrationTestUtils mTestUtils;
-    private LocaleTestUtils mLocaleTestUtils;
     private FakeAsyncTaskExecutor mFakeAsyncTaskExecutor;
     private CallDetailActivity mActivityUnderTest;
 
@@ -71,91 +57,25 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+
         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);
-        mTestUtils = new IntegrationTestUtils(getInstrumentation());
-        // Some of the tests rely on the text that appears on screen - safest to force a
-        // specific locale.
-        mLocaleTestUtils = new LocaleTestUtils(getInstrumentation().getTargetContext());
-        mLocaleTestUtils.setLocale(Locale.US);
     }
 
     @Override
     protected void tearDown() throws Exception {
-        mLocaleTestUtils.restoreLocale();
-        mLocaleTestUtils = null;
         cleanUpUri();
-        mTestUtils = null;
+
         AsyncTaskExecutors.setFactoryForTest(null);
         CallLogAsyncTaskUtil.resetForTest();
+
         super.tearDown();
     }
 
-    public void testInitialActivityStartsWithFetchingVoicemail() throws Throwable {
-        setActivityIntentForRealFileVoicemailEntry();
-        startActivityUnderTest();
-        // When the activity first starts, we will show "Loading voicemail" on the screen.
-        // The duration should not be visible.
-        assertHasOneTextViewContaining("Loading voicemail");
-        assertZeroTextViewsContaining("00:00");
-    }
-
-    public void testWhenCheckForContentCompletes() throws Throwable {
-        setActivityIntentForRealFileVoicemailEntry();
-        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.
-        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
-
-        // The voicemail async call may or may not return before we check the asserts.
-        assertHasOneTextViewContaining("Buffering", "00:00");
-        assertZeroTextViewsContaining("Loading voicemail");
-    }
-
-    public void testInvalidVoicemailShowsErrorMessage() throws Throwable {
-        setActivityIntentForTestVoicemailEntry();
-        startActivityUnderTest();
-        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
-        // 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.
-        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.
-        setActivityIntentForRealFileVoicemailEntry();
-        startActivityUnderTest();
-        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                getInstrumentation().callActivityOnPause(mActivityUnderTest);
-                getInstrumentation().callActivityOnResume(mActivityUnderTest);
-                getInstrumentation().callActivityOnPause(mActivityUnderTest);
-                getInstrumentation().callActivityOnResume(mActivityUnderTest);
-            }
-        });
-        assertHasOneTextViewContaining("Buffering", "00:00");
-    }
-
-    /**
-     * Test for bug where increase rate button with invalid voicemail causes a crash.
-     * <p>
-     * The repro steps for this crash were to open a voicemail that does not have an attachment,
-     * then click the play button (which just reported an error), then after that try to adjust the
-     * rate.  See http://b/5047879.
-     */
-    public void testClickIncreaseRateButtonWithInvalidVoicemailDoesNotCrash() throws Throwable {
-        setActivityIntentForTestVoicemailEntry();
-        startActivityUnderTest();
-        mTestUtils.clickButton(mActivityUnderTest, R.id.playback_start_stop);
-    }
-
     /** Test for bug where missing Extras on intent used to start Activity causes NPE. */
     public void testCallLogUriWithMissingExtrasShouldNotCauseNPE() throws Throwable {
         setActivityIntentForTestCallEntry();
@@ -170,35 +90,32 @@
     public void testVoicemailDoesNotHaveRemoveFromCallLog() throws Throwable {
         setActivityIntentForTestVoicemailEntry();
         startActivityUnderTest();
+        mFakeAsyncTaskExecutor.runTask(GET_CALL_DETAILS);
+
         Menu menu = new ContextMenuBuilder(mActivityUnderTest);
         mActivityUnderTest.onCreateOptionsMenu(menu);
         mActivityUnderTest.onPrepareOptionsMenu(menu);
         assertFalse(menu.findItem(R.id.menu_remove_from_call_log).isVisible());
+        assertTrue(menu.findItem(R.id.menu_trash).isVisible());
     }
 
-    /** Test to check that I haven't broken the remove-from-call-log entry from regular calls. */
+    /**
+     * Test to check that I haven't broken the remove-from-call-log entry from regular calls.
+     */
     public void testRegularCallDoesHaveRemoveFromCallLog() throws Throwable {
         setActivityIntentForTestCallEntry();
         startActivityUnderTest();
+        mFakeAsyncTaskExecutor.runTask(GET_CALL_DETAILS);
+
         Menu menu = new ContextMenuBuilder(mActivityUnderTest);
         mActivityUnderTest.onCreateOptionsMenu(menu);
         mActivityUnderTest.onPrepareOptionsMenu(menu);
         assertTrue(menu.findItem(R.id.menu_remove_from_call_log).isVisible());
-    }
-
-    @Suppress
-    public void testClickingCallStopsPlayback() throws Throwable {
-        setActivityIntentForRealFileVoicemailEntry();
-        startActivityUnderTest();
-        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
-        mTestUtils.clickButton(mActivityUnderTest, R.id.playback_speakerphone);
-        mTestUtils.clickButton(mActivityUnderTest, R.id.playback_start_stop);
-        Thread.sleep(2000);
-        // TODO: Suppressed the test for now, because I'm looking for an easy way to say "the audio
-        // is not playing at this point", and I can't find it without doing dirty things.
+        assertFalse(menu.findItem(R.id.menu_trash).isVisible());
     }
 
     private void setActivityIntentForTestCallEntry() {
+        assertNull(mVoicemailUri);
         assertNull(mCallLogUri);
         ContentResolver contentResolver = getContentResolver();
         ContentValues values = new ContentValues();
@@ -225,36 +142,6 @@
         setActivityIntent(intent);
     }
 
-    private void setActivityIntentForRealFileVoicemailEntry() throws IOException {
-        assertNull(mVoicemailUri);
-        ContentValues values = new ContentValues();
-        values.put(VoicemailContract.Voicemails.DATE, String.valueOf(System.currentTimeMillis()));
-        values.put(VoicemailContract.Voicemails.NUMBER, CONTACT_NUMBER);
-        values.put(VoicemailContract.Voicemails.MIME_TYPE, MIME_TYPE);
-        values.put(VoicemailContract.Voicemails.HAS_CONTENT, 1);
-        String packageName = getInstrumentation().getTargetContext().getPackageName();
-        mVoicemailUri = getContentResolver().insert(
-                VoicemailContract.Voicemails.buildSourceUri(packageName), values);
-        AssetManager assets = getAssets();
-        try (InputStream inputStream = assets.open(TEST_ASSET_NAME);
-             OutputStream outputStream = getContentResolver().openOutputStream(mVoicemailUri)) {
-            copyBetweenStreams(inputStream, outputStream);
-        }
-        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);
-    }
-
-    public void copyBetweenStreams(InputStream in, OutputStream out) throws IOException {
-        byte[] buffer = new byte[1024];
-        int bytesRead;
-        while ((bytesRead = in.read(buffer)) > 0) {
-            out.write(buffer, 0, bytesRead);
-        }
-    }
-
     private void cleanUpUri() {
         if (mVoicemailUri != null) {
             getContentResolver().delete(VoicemailContract.Voicemails.CONTENT_URI,
@@ -272,41 +159,9 @@
         return getInstrumentation().getTargetContext().getContentResolver();
     }
 
-    private TextView assertHasOneTextViewContaining(String text) throws Throwable {
-        assertNotNull(mActivityUnderTest);
-        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 assertHasOneTextViewContaining(String text1, String text2) throws Throwable {
-        assertNotNull(mActivityUnderTest);
-        List<TextView> view1s = mTestUtils.getTextViewsWithString(mActivityUnderTest, text1);
-        List<TextView> view2s = mTestUtils.getTextViewsWithString(mActivityUnderTest, text2);
-        assertEquals("There should have been one TextView with text '" + text1 + "' or text '"
-                + text2  + "' but found " + view1s + view2s, 1, view1s.size() + view2s.size());
-    }
-
-    private void assertZeroTextViewsContaining(String text) throws Throwable {
-        assertNotNull(mActivityUnderTest);
-        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 startActivityUnderTest() throws Throwable {
         assertNull(mActivityUnderTest);
         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(GET_CALL_DETAILS);
-        CallLogAsyncTaskUtil.resetForTest();
-    }
-
-    private AssetManager getAssets() {
-        return getInstrumentation().getContext().getAssets();
     }
 }
diff --git a/tests/src/com/android/dialer/calllog/CallLogFragmentTest.java b/tests/src/com/android/dialer/calllog/CallLogFragmentTest.java
index 71554d6..3f66d58 100644
--- a/tests/src/com/android/dialer/calllog/CallLogFragmentTest.java
+++ b/tests/src/com/android/dialer/calllog/CallLogFragmentTest.java
@@ -129,7 +129,14 @@
         mAdapter.pauseCache();
         mParentView = new FrameLayout(mActivity);
         mCursor = new MatrixCursor(CallLogQuery._PROJECTION);
-        mAdapter.setCursorForTesting(mCursor);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mAdapter.changeCursor(mCursor);
+            }
+        });
+        getInstrumentation().waitForIdleSync();
     }
 
     /**
@@ -316,7 +323,6 @@
     public void testBindView_CallButton() {
         mCursor.moveToFirst();
         insert(TEST_NUMBER, Calls.PRESENTATION_ALLOWED, NOW, 0, Calls.INCOMING_TYPE);
-        mAdapter.changeCursor(mCursor);
         CallLogListItemViewHolder viewHolder = (CallLogListItemViewHolder)
                 mAdapter.onCreateViewHolder(mParentView, /* viewType */ 0);
         bindViewForTest(viewHolder);
diff --git a/tests/src/com/android/dialer/voicemail/VoicemailPlaybackTest.java b/tests/src/com/android/dialer/voicemail/VoicemailPlaybackTest.java
new file mode 100644
index 0000000..dd86b0d
--- /dev/null
+++ b/tests/src/com/android/dialer/voicemail/VoicemailPlaybackTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2015 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.dialer.voicemail;
+
+import static com.android.dialer.voicemail.VoicemailPlaybackPresenter.Tasks.CHECK_FOR_CONTENT;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.net.Uri;
+import android.provider.VoicemailContract;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.contacts.common.test.IntegrationTestUtils;
+import com.android.dialer.R;
+import com.android.dialer.util.AsyncTaskExecutors;
+import com.android.dialer.util.FakeAsyncTaskExecutor;
+import com.android.dialer.util.LocaleTestUtils;
+import com.android.dialer.voicemail.VoicemailPlaybackLayout;
+import com.android.dialer.voicemail.VoicemailPlaybackPresenter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Unit tests for the {@link VoicemailPlaybackPresenter} and {@link VoicemailPlaybackLayout}.
+ */
+@LargeTest
+public class VoicemailPlaybackTest extends InstrumentationTestCase {
+    private static final String TEST_ASSET_NAME = "quick_test_recording.mp3";
+    private static final String MIME_TYPE = "audio/mp3";
+    private static final String CONTACT_NUMBER = "+1412555555";
+    private static final String VOICEMAIL_FILE_LOCATION = "/sdcard/sadlfj893w4j23o9sfu.mp3";
+
+    private Context mContext;
+    private VoicemailPlaybackPresenter mPresenter;
+    private VoicemailPlaybackLayout mLayout;
+
+    private Uri mVoicemailUri;
+    private IntegrationTestUtils mTestUtils;
+    private LocaleTestUtils mLocaleTestUtils;
+    private FakeAsyncTaskExecutor mFakeAsyncTaskExecutor;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mFakeAsyncTaskExecutor = new FakeAsyncTaskExecutor(getInstrumentation());
+        AsyncTaskExecutors.setFactoryForTest(mFakeAsyncTaskExecutor.getFactory());
+        mTestUtils = new IntegrationTestUtils(getInstrumentation());
+
+        // Some of the tests rely on the text - safest to force a specific locale.
+        mLocaleTestUtils = new LocaleTestUtils(getInstrumentation().getTargetContext());
+        mLocaleTestUtils.setLocale(Locale.US);
+
+        mContext = getInstrumentation().getTargetContext();
+        mLayout = new VoicemailPlaybackLayout(mContext);
+        mLayout.onFinishInflate();
+        mPresenter = new VoicemailPlaybackPresenter(mContext, null);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        cleanUpVoicemailUri();
+
+        mLocaleTestUtils.restoreLocale();
+        mLocaleTestUtils = null;
+
+        mLayout = null;
+        mPresenter = null;
+        mTestUtils = null;
+        AsyncTaskExecutors.setFactoryForTest(null);
+
+        super.tearDown();
+    }
+
+    public void testFetchingVoicemail() throws Throwable {
+        setUriForRealFileVoicemailEntry();
+        setPlaybackViewForPresenter();
+        assertHasOneTextViewContaining("Loading voicemail");
+    }
+
+    public void testWhenCheckForContentCompletes() throws Throwable {
+        setUriForRealFileVoicemailEntry();
+        setPlaybackViewForPresenter();
+
+        // 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.
+        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
+
+        assertHasOneTextViewContaining("Buffering");
+        assertHasZeroTextViewsContaining("Loading voicemail");
+    }
+
+    public void testInvalidVoicemailShowsErrorMessage() throws Throwable {
+        setUriForInvalidVoicemailEntry();
+        setPlaybackViewForPresenter();
+
+        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
+
+        // 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.
+        assertHasOneTextViewContaining("Couldn't play voicemail");
+        assertHasZeroTextViewsContaining("Buffering");
+    }
+
+    public void testClickingSpeakerphoneButton() throws Throwable {
+        setUriForRealFileVoicemailEntry();
+        setPlaybackViewForPresenter();
+
+        // Wait for check for content to complete.
+        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
+        getInstrumentation().waitForIdleSync();
+
+        // Force the speakerphone to false to start.
+        mPresenter.setSpeakerphoneOn(false);
+        assertFalse(mPresenter.isSpeakerphoneOn());
+
+        View speakerphoneButton = mLayout.findViewById(R.id.playback_speakerphone);
+        speakerphoneButton.performClick();
+        assertTrue(mPresenter.isSpeakerphoneOn());
+    }
+
+    private void cleanUpVoicemailUri() {
+        if (mVoicemailUri != null) {
+            getContentResolver().delete(VoicemailContract.Voicemails.CONTENT_URI,
+                    "_ID = ?", new String[] { String.valueOf(ContentUris.parseId(mVoicemailUri)) });
+            mVoicemailUri = null;
+        }
+    }
+
+    private void setUriForRealFileVoicemailEntry() throws IOException {
+        assertNull(mVoicemailUri);
+        ContentValues values = new ContentValues();
+        values.put(VoicemailContract.Voicemails.DATE, String.valueOf(System.currentTimeMillis()));
+        values.put(VoicemailContract.Voicemails.NUMBER, CONTACT_NUMBER);
+        values.put(VoicemailContract.Voicemails.MIME_TYPE, MIME_TYPE);
+        values.put(VoicemailContract.Voicemails.HAS_CONTENT, 1);
+        String packageName = getInstrumentation().getTargetContext().getPackageName();
+        mVoicemailUri = getContentResolver().insert(
+                VoicemailContract.Voicemails.buildSourceUri(packageName), values);
+        AssetManager assets = getAssets();
+        try (InputStream inputStream = assets.open(TEST_ASSET_NAME);
+             OutputStream outputStream = getContentResolver().openOutputStream(mVoicemailUri)) {
+            copyBetweenStreams(inputStream, outputStream);
+        }
+    }
+
+    private void setUriForInvalidVoicemailEntry() {
+        assertNull(mVoicemailUri);
+        ContentResolver contentResolver = getContentResolver();
+        ContentValues values = new ContentValues();
+        values.put(VoicemailContract.Voicemails.NUMBER, CONTACT_NUMBER);
+        values.put(VoicemailContract.Voicemails.HAS_CONTENT, 1);
+        values.put(VoicemailContract.Voicemails._DATA, VOICEMAIL_FILE_LOCATION);
+        mVoicemailUri = contentResolver.insert(VoicemailContract.Voicemails.CONTENT_URI, values);
+    }
+
+    private void setPlaybackViewForPresenter() {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mPresenter.setPlaybackView(mLayout, mVoicemailUri, false);
+            }
+        });
+    }
+
+    public void copyBetweenStreams(InputStream in, OutputStream out) throws IOException {
+        byte[] buffer = new byte[1024];
+        int bytesRead;
+        while ((bytesRead = in.read(buffer)) > 0) {
+            out.write(buffer, 0, bytesRead);
+        }
+    }
+
+    private void assertHasOneTextViewContaining(String text) throws Throwable {
+        assertNotNull(mLayout);
+        List<TextView> views = mTestUtils.getTextViewsWithString(mLayout, text);
+        assertEquals("There should have been one TextView with text '" + text + "' but found "
+                + views, 1, views.size());
+    }
+
+    private void assertHasZeroTextViewsContaining(String text) throws Throwable {
+        assertNotNull(mLayout);
+        List<TextView> views = mTestUtils.getTextViewsWithString(mLayout, text);
+        assertEquals("There should have been no TextViews with text '" + text + "' but found "
+                + views, 0,  views.size());
+    }
+
+    private ContentResolver getContentResolver() {
+        return getInstrumentation().getTargetContext().getContentResolver();
+    }
+
+    private AssetManager getAssets() {
+        return getInstrumentation().getContext().getAssets();
+    }
+}