Remove "Remove from call log" from menu for voicemails.

- When looking at a voicemail we shouldn't remove from call log, because
  we can just hit the trash button.

Bug: 5054103
Change-Id: I97c5870f12a7d495d2c49bb94f0795a1b3e12f9e
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index 14fec85..324ab20 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -169,7 +169,6 @@
         });
     }
 
-
     @Override
     public void onResume() {
         super.onResume();
@@ -184,24 +183,31 @@
      * playback.  If it doesn't, then hide the voicemail ui.
      */
     private void optionallyHandleVoicemail() {
-        Uri voicemailUri = getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI);
-        FragmentManager manager = getFragmentManager();
-        VoicemailPlaybackFragment fragment = (VoicemailPlaybackFragment) manager.findFragmentById(
-                R.id.voicemail_playback_fragment);
-        if (voicemailUri != null) {
-            // Has voicemail uri: leave the fragment visible.  Optionally start the playback.
+        if (hasVoicemail()) {
+            // Has voicemail: leave the fragment visible.  Optionally start the playback.
             // Do a query to fetch the voicemail status messages.
             boolean startPlayback = getIntent().getBooleanExtra(
                     EXTRA_VOICEMAIL_START_PLAYBACK, false);
-            fragment.setVoicemailUri(voicemailUri, startPlayback);
+            Uri voicemailUri = getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI);
+            getVoicemailPlaybackFragment().setVoicemailUri(voicemailUri, startPlayback);
             mAsyncQueryHandler.startVoicemailStatusQuery(voicemailUri);
         } else {
             // No voicemail uri: hide the voicemail fragment and the status view.
-            manager.beginTransaction().hide(fragment).commit();
+            getFragmentManager().beginTransaction().hide(getVoicemailPlaybackFragment()).commit();
             mStatusMessageView.setVisibility(View.GONE);
         }
     }
 
+    private boolean hasVoicemail() {
+        return getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI) != null;
+    }
+
+    private VoicemailPlaybackFragment getVoicemailPlaybackFragment() {
+        FragmentManager manager = getFragmentManager();
+        return (VoicemailPlaybackFragment) manager.findFragmentById(
+                R.id.voicemail_playback_fragment);
+    }
+
     /**
      * Returns the list of URIs to show.
      * <p>
@@ -349,24 +355,27 @@
         }
 
         // This action deletes all elements in the group from the call log.
-        actions.add(new ViewEntry(android.R.drawable.ic_menu_close_clear_cancel,
-                getString(R.string.recentCalls_removeFromRecentList),
-                new View.OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        StringBuilder callIds = new StringBuilder();
-                        for (Uri callUri : callUris) {
-                            if (callIds.length() != 0) {
-                                callIds.append(",");
+        // We don't have this action for voicemails, because you can just use the trash button.
+        if (!hasVoicemail()) {
+            actions.add(new ViewEntry(android.R.drawable.ic_menu_close_clear_cancel,
+                    getString(R.string.recentCalls_removeFromRecentList),
+                    new View.OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                            StringBuilder callIds = new StringBuilder();
+                            for (Uri callUri : callUris) {
+                                if (callIds.length() != 0) {
+                                    callIds.append(",");
+                                }
+                                callIds.append(ContentUris.parseId(callUri));
                             }
-                            callIds.append(ContentUris.parseId(callUri));
-                        }
 
-                        getContentResolver().delete(Calls.CONTENT_URI_WITH_VOICEMAIL,
-                                Calls._ID + " IN (" + callIds + ")", null);
-                        finish();
-                    }
-                }));
+                            getContentResolver().delete(Calls.CONTENT_URI_WITH_VOICEMAIL,
+                                    Calls._ID + " IN (" + callIds + ")", null);
+                            finish();
+                        }
+                    }));
+        }
 
         if (canPlaceCallsTo && !isSipNumber && !isVoicemailNumber) {
             // "Edit the number before calling" is only available for PSTN numbers.
diff --git a/tests/src/com/android/contacts/CallDetailActivityTest.java b/tests/src/com/android/contacts/CallDetailActivityTest.java
index a36216e..665bc92 100644
--- a/tests/src/com/android/contacts/CallDetailActivityTest.java
+++ b/tests/src/com/android/contacts/CallDetailActivityTest.java
@@ -17,6 +17,7 @@
 package com.android.contacts;
 
 import com.android.contacts.util.IntegrationTestUtils;
+import com.android.contacts.util.LocaleTestUtils;
 import com.google.common.base.Preconditions;
 
 import android.app.Activity;
@@ -28,6 +29,8 @@
 import android.provider.CallLog;
 import android.test.ActivityInstrumentationTestCase2;
 
+import java.util.Locale;
+
 /**
  * Unit tests for the {@link CallDetailActivity}.
  */
@@ -35,6 +38,7 @@
     private static final String FAKE_VOICEMAIL_URI_STRING = "content://fake_uri";
     private Uri mUri;
     private IntegrationTestUtils mTestUtils;
+    private LocaleTestUtils mLocaleTestUtils;
 
     public CallDetailActivityTest() {
         super(CallDetailActivity.class);
@@ -47,10 +51,16 @@
         // 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;
         super.tearDown();
@@ -61,7 +71,7 @@
      * <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.
+     * rate.  See http://b/5047879.
      */
     public void testClickIncreaseRateButtonWithInvalidVoicemailDoesNotCrash() throws Throwable {
         setActivityIntentForTestVoicemailEntry();
@@ -76,6 +86,28 @@
         getActivity();
     }
 
+    /**
+     * Test for bug where voicemails should not have remove-from-call-log entry.
+     * <p>
+     * See http://b/5054103.
+     */
+    public void testVoicemailDoesNotHaveRemoveFromCallLog() throws Throwable {
+        setActivityIntentForTestVoicemailEntry();
+        getActivity();
+        assertEquals(0, countTextViewsContaining("Remove from call log"));
+    }
+
+    /** Test to check that I haven't broken the remove-from-call-log entry from regular calls. */
+    public void testRegularCallDoesHaveRemoveFromCallLog() throws Throwable {
+        setActivityIntentForTestCallEntry();
+        getActivity();
+        assertEquals(1, countTextViewsContaining("Remove from call log"));
+    }
+
+    private int countTextViewsContaining(String text) throws Throwable {
+        return mTestUtils.getTextViewsWithString(getActivity(), text).size();
+    }
+
     private void setActivityIntentForTestCallEntry() {
         createTestCallEntry(false);
         setActivityIntent(new Intent(Intent.ACTION_VIEW, mUri));
diff --git a/tests/src/com/android/contacts/util/IntegrationTestUtils.java b/tests/src/com/android/contacts/util/IntegrationTestUtils.java
index 45dc981..a61ea57 100644
--- a/tests/src/com/android/contacts/util/IntegrationTestUtils.java
+++ b/tests/src/com/android/contacts/util/IntegrationTestUtils.java
@@ -27,9 +27,13 @@
 import android.content.Context;
 import android.os.PowerManager;
 import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
 
 import junit.framework.Assert;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.FutureTask;
@@ -118,4 +122,55 @@
             }
         }
     }
+
+    /**
+     * Gets all {@link TextView} objects whose {@link TextView#getText()} contains the given text as
+     * a substring.
+     */
+    public List<TextView> getTextViewsWithString(final Activity activity, final String text)
+            throws Throwable {
+        return runOnUiThreadAndGetTheResult(new Callable<List<TextView>>() {
+            @Override
+            public List<TextView> call() throws Exception {
+                List<TextView> matchingViews = new ArrayList<TextView>();
+                for (TextView textView : getAllViews(TextView.class, getRootView(activity))) {
+                    if (textView.getText().toString().contains(text)) {
+                        matchingViews.add(textView);
+                    }
+                }
+                return matchingViews;
+            }
+        });
+    }
+
+    /** Find the root view for a given activity. */
+    public static View getRootView(Activity activity) {
+        return activity.findViewById(android.R.id.content).getRootView();
+    }
+
+    /**
+     * Gets a list of all views of a given type, rooted at the given parent.
+     * <p>
+     * This method will recurse down through all {@link ViewGroup} instances looking for
+     * {@link View} instances of the supplied class type. Specifically it will use the
+     * {@link Class#isAssignableFrom(Class)} method as the test for which views to add to the list,
+     * so if you provide {@code View.class} as your type, you will get every view. The parent itself
+     * will be included also, should it be of the right type.
+     * <p>
+     * This call manipulates the ui, and as such should only be called from the application's main
+     * thread.
+     */
+    private static <T extends View> List<T> getAllViews(final Class<T> clazz, final View parent) {
+        List<T> results = new ArrayList<T>();
+        if (parent.getClass().equals(clazz)) {
+            results.add(clazz.cast(parent));
+        }
+        if (parent instanceof ViewGroup) {
+            ViewGroup viewGroup = (ViewGroup) parent;
+            for (int i = 0; i < viewGroup.getChildCount(); ++i) {
+                results.addAll(getAllViews(clazz, viewGroup.getChildAt(i)));
+            }
+        }
+        return results;
+    }
 }