1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.contacts;
18
19import static com.android.contacts.CallDetailActivity.Tasks.UPDATE_PHONE_CALL_DETAILS;
20import static com.android.contacts.voicemail.VoicemailPlaybackPresenter.Tasks.CHECK_FOR_CONTENT;
21import static com.android.contacts.voicemail.VoicemailPlaybackPresenter.Tasks.PREPARE_MEDIA_PLAYER;
22
23import android.content.ContentResolver;
24import android.content.ContentUris;
25import android.content.ContentValues;
26import android.content.Intent;
27import android.content.res.AssetManager;
28import android.net.Uri;
29import android.provider.CallLog;
30import android.provider.VoicemailContract;
31import android.test.ActivityInstrumentationTestCase2;
32import android.test.suitebuilder.annotation.LargeTest;
33import android.test.suitebuilder.annotation.Suppress;
34import android.view.Menu;
35import android.widget.TextView;
36
37import com.android.contacts.util.AsyncTaskExecutors;
38import com.android.contacts.util.FakeAsyncTaskExecutor;
39import com.android.contacts.util.IntegrationTestUtils;
40import com.android.contacts.util.LocaleTestUtils;
41import com.android.internal.view.menu.ContextMenuBuilder;
42import com.google.common.io.Closeables;
43
44import java.io.IOException;
45import java.io.InputStream;
46import java.io.OutputStream;
47import java.util.List;
48import java.util.Locale;
49
50/**
51 * Unit tests for the {@link CallDetailActivity}.
52 */
53@LargeTest
54public class CallDetailActivityTest extends ActivityInstrumentationTestCase2<CallDetailActivity> {
55    private static final String TEST_ASSET_NAME = "quick_test_recording.mp3";
56    private static final String MIME_TYPE = "audio/mp3";
57    private static final String CONTACT_NUMBER = "+1412555555";
58    private static final String VOICEMAIL_FILE_LOCATION = "/sdcard/sadlfj893w4j23o9sfu.mp3";
59
60    private Uri mCallLogUri;
61    private Uri mVoicemailUri;
62    private IntegrationTestUtils mTestUtils;
63    private LocaleTestUtils mLocaleTestUtils;
64    private FakeAsyncTaskExecutor mFakeAsyncTaskExecutor;
65    private CallDetailActivity mActivityUnderTest;
66
67    public CallDetailActivityTest() {
68        super(CallDetailActivity.class);
69    }
70
71    @Override
72    protected void setUp() throws Exception {
73        super.setUp();
74        mFakeAsyncTaskExecutor = new FakeAsyncTaskExecutor(getInstrumentation());
75        AsyncTaskExecutors.setFactoryForTest(mFakeAsyncTaskExecutor.getFactory());
76        // I don't like the default of focus-mode for tests, the green focus border makes the
77        // screenshots look weak.
78        setActivityInitialTouchMode(true);
79        mTestUtils = new IntegrationTestUtils(getInstrumentation());
80        // Some of the tests rely on the text that appears on screen - safest to force a
81        // specific locale.
82        mLocaleTestUtils = new LocaleTestUtils(getInstrumentation().getTargetContext());
83        mLocaleTestUtils.setLocale(Locale.US);
84    }
85
86    @Override
87    protected void tearDown() throws Exception {
88        mLocaleTestUtils.restoreLocale();
89        mLocaleTestUtils = null;
90        cleanUpUri();
91        mTestUtils = null;
92        AsyncTaskExecutors.setFactoryForTest(null);
93        super.tearDown();
94    }
95
96    public void testInitialActivityStartsWithFetchingVoicemail() throws Throwable {
97        setActivityIntentForTestVoicemailEntry();
98        startActivityUnderTest();
99        // When the activity first starts, we will show "Fetching voicemail" on the screen.
100        // The duration should not be visible.
101        assertHasOneTextViewContaining("Fetching voicemail");
102        assertZeroTextViewsContaining("00:00");
103    }
104
105    public void testWhenCheckForContentCompletes_UiShowsBuffering() throws Throwable {
106        setActivityIntentForTestVoicemailEntry();
107        startActivityUnderTest();
108        // There is a background check that is testing to see if we have the content available.
109        // Once that task completes, we shouldn't be showing the fetching message, we should
110        // be showing "Buffering".
111        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
112        assertHasOneTextViewContaining("Buffering");
113        assertZeroTextViewsContaining("Fetching voicemail");
114    }
115
116    public void testInvalidVoicemailShowsErrorMessage() throws Throwable {
117        setActivityIntentForTestVoicemailEntry();
118        startActivityUnderTest();
119        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
120        // There should be exactly one background task ready to prepare the media player.
121        // Preparing the media player will have thrown an IOException since the file doesn't exist.
122        // This should have put a failed to play message on screen, buffering is gone.
123        mFakeAsyncTaskExecutor.runTask(PREPARE_MEDIA_PLAYER);
124        assertHasOneTextViewContaining("Couldn't play voicemail");
125        assertZeroTextViewsContaining("Buffering");
126    }
127
128    public void testOnResumeDoesNotCreateManyFragments() throws Throwable {
129        // There was a bug where every time the activity was resumed, a new fragment was created.
130        // Before the fix, this was failing reproducibly with at least 3 "Buffering" views.
131        setActivityIntentForTestVoicemailEntry();
132        startActivityUnderTest();
133        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
134        getInstrumentation().runOnMainSync(new Runnable() {
135            @Override
136            public void run() {
137                getInstrumentation().callActivityOnPause(mActivityUnderTest);
138                getInstrumentation().callActivityOnResume(mActivityUnderTest);
139                getInstrumentation().callActivityOnPause(mActivityUnderTest);
140                getInstrumentation().callActivityOnResume(mActivityUnderTest);
141            }
142        });
143        assertHasOneTextViewContaining("Buffering");
144    }
145
146    /**
147     * Test for bug where increase rate button with invalid voicemail causes a crash.
148     * <p>
149     * The repro steps for this crash were to open a voicemail that does not have an attachment,
150     * then click the play button (which just reported an error), then after that try to adjust the
151     * rate.  See http://b/5047879.
152     */
153    public void testClickIncreaseRateButtonWithInvalidVoicemailDoesNotCrash() throws Throwable {
154        setActivityIntentForTestVoicemailEntry();
155        startActivityUnderTest();
156        mTestUtils.clickButton(mActivityUnderTest, R.id.playback_start_stop);
157        mTestUtils.clickButton(mActivityUnderTest, R.id.rate_increase_button);
158    }
159
160    /** Test for bug where missing Extras on intent used to start Activity causes NPE. */
161    public void testCallLogUriWithMissingExtrasShouldNotCauseNPE() throws Throwable {
162        setActivityIntentForTestCallEntry();
163        startActivityUnderTest();
164    }
165
166    /**
167     * Test for bug where voicemails should not have remove-from-call-log entry.
168     * <p>
169     * See http://b/5054103.
170     */
171    public void testVoicemailDoesNotHaveRemoveFromCallLog() throws Throwable {
172        setActivityIntentForTestVoicemailEntry();
173        startActivityUnderTest();
174        Menu menu = new ContextMenuBuilder(mActivityUnderTest);
175        mActivityUnderTest.onCreateOptionsMenu(menu);
176        mActivityUnderTest.onPrepareOptionsMenu(menu);
177        assertFalse(menu.findItem(R.id.menu_remove_from_call_log).isVisible());
178    }
179
180    /** Test to check that I haven't broken the remove-from-call-log entry from regular calls. */
181    public void testRegularCallDoesHaveRemoveFromCallLog() throws Throwable {
182        setActivityIntentForTestCallEntry();
183        startActivityUnderTest();
184        Menu menu = new ContextMenuBuilder(mActivityUnderTest);
185        mActivityUnderTest.onCreateOptionsMenu(menu);
186        mActivityUnderTest.onPrepareOptionsMenu(menu);
187        assertTrue(menu.findItem(R.id.menu_remove_from_call_log).isVisible());
188    }
189
190    /**
191     * Test to show that we are correctly displaying playback rate on the ui.
192     * <p>
193     * See bug http://b/5044075.
194     */
195    @Suppress
196    public void testVoicemailPlaybackRateDisplayedOnUi() throws Throwable {
197        setActivityIntentForTestVoicemailEntry();
198        startActivityUnderTest();
199        // Find the TextView containing the duration.  It should be initially displaying "00:00".
200        List<TextView> views = mTestUtils.getTextViewsWithString(mActivityUnderTest, "00:00");
201        assertEquals(1, views.size());
202        TextView timeDisplay = views.get(0);
203        // Hit the plus button.  At this point we should be displaying "fast speed".
204        mTestUtils.clickButton(mActivityUnderTest, R.id.rate_increase_button);
205        assertEquals("fast speed", mTestUtils.getText(timeDisplay));
206        // Hit the minus button.  We should be back to "normal" speed.
207        mTestUtils.clickButton(mActivityUnderTest, R.id.rate_decrease_button);
208        assertEquals("normal speed", mTestUtils.getText(timeDisplay));
209        // Wait for one and a half seconds.  The timer will be back.
210        Thread.sleep(1500);
211        assertEquals("00:00", mTestUtils.getText(timeDisplay));
212    }
213
214    @Suppress
215    public void testClickingCallStopsPlayback() throws Throwable {
216        setActivityIntentForRealFileVoicemailEntry();
217        startActivityUnderTest();
218        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
219        mFakeAsyncTaskExecutor.runTask(PREPARE_MEDIA_PLAYER);
220        mTestUtils.clickButton(mActivityUnderTest, R.id.playback_speakerphone);
221        mTestUtils.clickButton(mActivityUnderTest, R.id.playback_start_stop);
222        mTestUtils.clickButton(mActivityUnderTest, R.id.call_and_sms_main_action);
223        Thread.sleep(2000);
224        // TODO: Suppressed the test for now, because I'm looking for an easy way to say "the audio
225        // is not playing at this point", and I can't find it without doing dirty things.
226    }
227
228    private void setActivityIntentForTestCallEntry() {
229        assertNull(mCallLogUri);
230        ContentResolver contentResolver = getContentResolver();
231        ContentValues values = new ContentValues();
232        values.put(CallLog.Calls.NUMBER, CONTACT_NUMBER);
233        values.put(CallLog.Calls.TYPE, CallLog.Calls.INCOMING_TYPE);
234        mCallLogUri = contentResolver.insert(CallLog.Calls.CONTENT_URI, values);
235        setActivityIntent(new Intent(Intent.ACTION_VIEW, mCallLogUri));
236    }
237
238    private void setActivityIntentForTestVoicemailEntry() {
239        assertNull(mVoicemailUri);
240        ContentResolver contentResolver = getContentResolver();
241        ContentValues values = new ContentValues();
242        values.put(VoicemailContract.Voicemails.NUMBER, CONTACT_NUMBER);
243        values.put(VoicemailContract.Voicemails.HAS_CONTENT, 1);
244        values.put(VoicemailContract.Voicemails._DATA, VOICEMAIL_FILE_LOCATION);
245        mVoicemailUri = contentResolver.insert(VoicemailContract.Voicemails.CONTENT_URI, values);
246        Uri callLogUri = ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
247                ContentUris.parseId(mVoicemailUri));
248        Intent intent = new Intent(Intent.ACTION_VIEW, callLogUri);
249        intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI, mVoicemailUri);
250        setActivityIntent(intent);
251    }
252
253    private void setActivityIntentForRealFileVoicemailEntry() throws IOException {
254        assertNull(mVoicemailUri);
255        ContentValues values = new ContentValues();
256        values.put(VoicemailContract.Voicemails.DATE, String.valueOf(System.currentTimeMillis()));
257        values.put(VoicemailContract.Voicemails.NUMBER, CONTACT_NUMBER);
258        values.put(VoicemailContract.Voicemails.MIME_TYPE, MIME_TYPE);
259        values.put(VoicemailContract.Voicemails.HAS_CONTENT, 1);
260        String packageName = getInstrumentation().getTargetContext().getPackageName();
261        mVoicemailUri = getContentResolver().insert(
262                VoicemailContract.Voicemails.buildSourceUri(packageName), values);
263        AssetManager assets = getAssets();
264        OutputStream outputStream = null;
265        InputStream inputStream = null;
266        try {
267            inputStream = assets.open(TEST_ASSET_NAME);
268            outputStream = getContentResolver().openOutputStream(mVoicemailUri);
269            copyBetweenStreams(inputStream, outputStream);
270        } finally {
271            Closeables.closeQuietly(outputStream);
272            Closeables.closeQuietly(inputStream);
273        }
274        Uri callLogUri = ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
275                ContentUris.parseId(mVoicemailUri));
276        Intent intent = new Intent(Intent.ACTION_VIEW, callLogUri);
277        intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI, mVoicemailUri);
278        setActivityIntent(intent);
279    }
280
281    public void copyBetweenStreams(InputStream in, OutputStream out) throws IOException {
282        byte[] buffer = new byte[1024];
283        int bytesRead;
284        int total = 0;
285        while ((bytesRead = in.read(buffer)) != -1) {
286            total += bytesRead;
287            out.write(buffer, 0, bytesRead);
288        }
289    }
290
291    private void cleanUpUri() {
292        if (mVoicemailUri != null) {
293            getContentResolver().delete(VoicemailContract.Voicemails.CONTENT_URI,
294                    "_ID = ?", new String[] { String.valueOf(ContentUris.parseId(mVoicemailUri)) });
295            mVoicemailUri = null;
296        }
297        if (mCallLogUri != null) {
298            getContentResolver().delete(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
299                    "_ID = ?", new String[] { String.valueOf(ContentUris.parseId(mCallLogUri)) });
300            mCallLogUri = null;
301        }
302    }
303
304    private ContentResolver getContentResolver() {
305        return getInstrumentation().getTargetContext().getContentResolver();
306    }
307
308    private TextView assertHasOneTextViewContaining(String text) throws Throwable {
309        assertNotNull(mActivityUnderTest);
310        List<TextView> views = mTestUtils.getTextViewsWithString(mActivityUnderTest, text);
311        assertEquals("There should have been one TextView with text '" + text + "' but found "
312                + views, 1, views.size());
313        return views.get(0);
314    }
315
316    private void assertZeroTextViewsContaining(String text) throws Throwable {
317        assertNotNull(mActivityUnderTest);
318        List<TextView> views = mTestUtils.getTextViewsWithString(mActivityUnderTest, text);
319        assertEquals("There should have been no TextViews with text '" + text + "' but found "
320                + views, 0,  views.size());
321    }
322
323    private void startActivityUnderTest() throws Throwable {
324        assertNull(mActivityUnderTest);
325        mActivityUnderTest = getActivity();
326        assertNotNull("activity should not be null", mActivityUnderTest);
327        // We have to run all tasks, not just one.
328        // This is because it seems that we can have onResume, onPause, onResume during the course
329        // of a single unit test.
330        mFakeAsyncTaskExecutor.runAllTasks(UPDATE_PHONE_CALL_DETAILS);
331    }
332
333    private AssetManager getAssets() {
334        return getInstrumentation().getContext().getAssets();
335    }
336}
337