summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorChiao Cheng <chiaocheng@google.com>2012-08-17 16:59:12 -0700
committerChiao Cheng <chiaocheng@google.com>2012-08-21 13:31:19 -0700
commit94b10b530c0fc297e2974e57e094c500d3ee6003 (patch)
treeb74d663c2663b5db2f6da888081648ce054480f5 /tests
parentdab5cd8890c0d0ca9001a13c2197114a4002338a (diff)
Initial move of dialer features from contacts app.
Bug: 6993891 Change-Id: I758ce359ca7e87a1d184303822979318be171921
Diffstat (limited to 'tests')
-rw-r--r--tests/Android.mk19
-rw-r--r--tests/AndroidManifest.xml62
-rw-r--r--tests/proguard.flags20
-rw-r--r--tests/res/drawable/default_icon.pngbin0 -> 4005 bytes
-rw-r--r--tests/res/drawable/phone_icon.pngbin0 -> 3621 bytes
-rw-r--r--tests/res/layout/fill_call_log_test.xml56
-rw-r--r--tests/res/values/donottranslate_strings.xml41
-rw-r--r--tests/res/xml/iconset.xml24
-rw-r--r--tests/src/com/android/dialer/CallDetailActivityTest.java336
-rw-r--r--tests/src/com/android/dialer/DialerLaunchPerformance.java49
-rw-r--r--tests/src/com/android/dialer/PhoneCallDetailsHelperTest.java328
-rw-r--r--tests/src/com/android/dialer/calllog/CallLogAdapterTest.java224
-rw-r--r--tests/src/com/android/dialer/calllog/CallLogFragmentTest.java632
-rw-r--r--tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java345
-rw-r--r--tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java151
-rw-r--r--tests/src/com/android/dialer/calllog/CallLogQueryTestUtils.java46
-rw-r--r--tests/src/com/android/dialer/calllog/TestPhoneNumberHelper.java37
-rw-r--r--tests/src/com/android/dialer/tests/calllog/FillCallLogTestActivity.java308
-rw-r--r--tests/src/com/android/dialer/util/ExpirableCacheTest.java125
-rw-r--r--tests/src/com/android/dialer/util/FakeAsyncTaskExecutor.java233
-rw-r--r--tests/src/com/android/dialer/util/LocaleTestUtils.java119
-rw-r--r--tests/src/com/android/dialer/voicemail/VoicemailStatusHelperImplTest.java274
22 files changed, 3429 insertions, 0 deletions
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 000000000..d440f6a6d
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,19 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+LOCAL_CERTIFICATE := shared
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES += com.android.contacts.common.test
+
+LOCAL_PACKAGE_NAME := DialerTests
+
+LOCAL_INSTRUMENTATION_FOR := Dialer
+
+include $(BUILD_PACKAGE)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644
index 000000000..3a714e39a
--- /dev/null
+++ b/tests/AndroidManifest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2012 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.dialer.tests">
+
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
+ <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+
+ <uses-permission android:name="android.permission.USE_CREDENTIALS" />
+ <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
+ <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
+ <uses-permission android:name="android.permission.READ_SYNC_STATS" />
+ <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+
+ <uses-permission android:name="android.permission.READ_PROFILE" />
+ <uses-permission android:name="android.permission.READ_SOCIAL_STREAM" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <meta-data android:name="com.android.dialer.iconset" android:resource="@xml/iconset" />
+
+ <activity android:name=".calllog.FillCallLogTestActivity"
+ android:label="Call log filter test"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.dialer"
+ android:label="Dialer app tests">
+ </instrumentation>
+
+ <instrumentation android:name="com.android.dialer.DialerLaunchPerformance"
+ android:targetPackage="com.android.dialer"
+ android:label="Dialer launch performance">
+ </instrumentation>
+
+</manifest>
diff --git a/tests/proguard.flags b/tests/proguard.flags
new file mode 100644
index 000000000..39784b11b
--- /dev/null
+++ b/tests/proguard.flags
@@ -0,0 +1,20 @@
+-keep class com.android.contacts.model.Sources {
+ public <init>(...);
+}
+
+# Xml files containing onClick (menus and layouts) require that proguard not
+# remove their handlers.
+-keepclassmembers class * extends android.app.Activity {
+ public void *(android.view.View);
+ public void *(android.view.MenuItem);
+}
+
+# Any class or method annotated with NeededForTesting or NeededForReflection.
+-keep @com.android.contacts.test.NeededForTesting class *
+-keep @com.android.contacts.test.NeededForReflection class *
+-keepclassmembers class * {
+@com.android.contacts.test.NeededForTesting *;
+@com.android.contacts.test.NeededForReflection *;
+}
+
+-verbose
diff --git a/tests/res/drawable/default_icon.png b/tests/res/drawable/default_icon.png
new file mode 100644
index 000000000..cea0eb3b7
--- /dev/null
+++ b/tests/res/drawable/default_icon.png
Binary files differ
diff --git a/tests/res/drawable/phone_icon.png b/tests/res/drawable/phone_icon.png
new file mode 100644
index 000000000..4e613ecce
--- /dev/null
+++ b/tests/res/drawable/phone_icon.png
Binary files differ
diff --git a/tests/res/layout/fill_call_log_test.xml b/tests/res/layout/fill_call_log_test.xml
new file mode 100644
index 000000000..704b9c66e
--- /dev/null
+++ b/tests/res/layout/fill_call_log_test.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2012 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
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+>
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/numberOfCallLogEntries"
+ />
+ <EditText
+ android:id="@+id/number"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="number"
+ android:text="10"
+ />
+ <CheckBox
+ android:id="@+id/use_random_numbers"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/useRandomNumbers"
+ />
+ <Button
+ android:id="@+id/add"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/addToCallLogButton"
+ />
+ <ProgressBar
+ android:id="@+id/progress"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:indeterminate="false"
+ android:visibility="gone"
+ />
+</LinearLayout>
diff --git a/tests/res/values/donottranslate_strings.xml b/tests/res/values/donottranslate_strings.xml
new file mode 100644
index 000000000..ceba5ea96
--- /dev/null
+++ b/tests/res/values/donottranslate_strings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2012 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
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <string-array name="allIntents">
+ <!-- List modes -->
+ <!-- Various ways to start Contacts -->
+ <item>DIAL</item>
+ <item>DIAL phone (deprecated)</item>
+ <item>DIAL person (deprecated)</item>
+ <item>DIAL voicemail</item>
+ <item>CALL BUTTON</item>
+ <item>DIAL tel</item>
+ <item>VIEW tel</item>
+ <item>VIEW calls (call-log after a phone call)</item>
+ <item>VIEW calls item</item>
+ <item>CallDetailActivity (legacy)</item>
+ <item>CallLogActivity (legacy)</item>
+ </string-array>
+
+ <string name="addToCallLogButton">Add</string>
+ <string name="useRandomNumbers">Use random numbers</string>
+ <string name="numberOfCallLogEntries">Number of call log entries to add:</string>
+ <string name="addedLogEntriesToast">Added %1$d call log entries.</string>
+ <string name="noLogEntriesToast">No entries in the call log yet. Need at least one record for the template. Or use random numbers.</string>
+
+</resources>
diff --git a/tests/res/xml/iconset.xml b/tests/res/xml/iconset.xml
new file mode 100644
index 000000000..ec3894566
--- /dev/null
+++ b/tests/res/xml/iconset.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2012 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
+ -->
+
+<icon-set xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <icon-default android:icon="@drawable/default_icon" />
+ <icon android:mimeType="vnd.android.cursor.item/phone"
+ android:icon="@drawable/phone_icon" />
+
+</icon-set>
diff --git a/tests/src/com/android/dialer/CallDetailActivityTest.java b/tests/src/com/android/dialer/CallDetailActivityTest.java
new file mode 100644
index 000000000..43204652a
--- /dev/null
+++ b/tests/src/com/android/dialer/CallDetailActivityTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer;
+
+import static com.android.dialer.CallDetailActivity.Tasks.UPDATE_PHONE_CALL_DETAILS;
+import static com.android.dialer.voicemail.VoicemailPlaybackPresenter.Tasks.CHECK_FOR_CONTENT;
+import static com.android.dialer.voicemail.VoicemailPlaybackPresenter.Tasks.PREPARE_MEDIA_PLAYER;
+
+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;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.view.Menu;
+import android.widget.TextView;
+
+import com.android.contacts.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 com.google.common.io.Closeables;
+
+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}.
+ */
+@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;
+
+ public CallDetailActivityTest() {
+ super(CallDetailActivity.class);
+ }
+
+ @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);
+ super.tearDown();
+ }
+
+ public void testInitialActivityStartsWithFetchingVoicemail() throws Throwable {
+ setActivityIntentForTestVoicemailEntry();
+ startActivityUnderTest();
+ // When the activity first starts, we will show "Fetching voicemail" on the screen.
+ // The duration should not be visible.
+ assertHasOneTextViewContaining("Fetching voicemail");
+ assertZeroTextViewsContaining("00:00");
+ }
+
+ public void testWhenCheckForContentCompletes_UiShowsBuffering() throws Throwable {
+ setActivityIntentForTestVoicemailEntry();
+ startActivityUnderTest();
+ // There is a background check that is testing to see if we have the content available.
+ // Once that task completes, we shouldn't be showing the fetching message, we should
+ // be showing "Buffering".
+ mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
+ assertHasOneTextViewContaining("Buffering");
+ assertZeroTextViewsContaining("Fetching voicemail");
+ }
+
+ public void testInvalidVoicemailShowsErrorMessage() throws Throwable {
+ setActivityIntentForTestVoicemailEntry();
+ startActivityUnderTest();
+ mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
+ // There should be exactly one background task ready to prepare the media player.
+ // Preparing the media player will have thrown an IOException since the file doesn't exist.
+ // This should have put a failed to play message on screen, buffering is gone.
+ mFakeAsyncTaskExecutor.runTask(PREPARE_MEDIA_PLAYER);
+ assertHasOneTextViewContaining("Couldn't play voicemail");
+ assertZeroTextViewsContaining("Buffering");
+ }
+
+ public void testOnResumeDoesNotCreateManyFragments() throws Throwable {
+ // There was a bug where every time the activity was resumed, a new fragment was created.
+ // Before the fix, this was failing reproducibly with at least 3 "Buffering" views.
+ setActivityIntentForTestVoicemailEntry();
+ 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");
+ }
+
+ /**
+ * 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);
+ mTestUtils.clickButton(mActivityUnderTest, R.id.rate_increase_button);
+ }
+
+ /** Test for bug where missing Extras on intent used to start Activity causes NPE. */
+ public void testCallLogUriWithMissingExtrasShouldNotCauseNPE() throws Throwable {
+ setActivityIntentForTestCallEntry();
+ startActivityUnderTest();
+ }
+
+ /**
+ * Test for bug where voicemails should not have remove-from-call-log entry.
+ * <p>
+ * See http://b/5054103.
+ */
+ public void testVoicemailDoesNotHaveRemoveFromCallLog() throws Throwable {
+ setActivityIntentForTestVoicemailEntry();
+ startActivityUnderTest();
+ Menu menu = new ContextMenuBuilder(mActivityUnderTest);
+ mActivityUnderTest.onCreateOptionsMenu(menu);
+ mActivityUnderTest.onPrepareOptionsMenu(menu);
+ assertFalse(menu.findItem(R.id.menu_remove_from_call_log).isVisible());
+ }
+
+ /** Test to check that I haven't broken the remove-from-call-log entry from regular calls. */
+ public void testRegularCallDoesHaveRemoveFromCallLog() throws Throwable {
+ setActivityIntentForTestCallEntry();
+ startActivityUnderTest();
+ Menu menu = new ContextMenuBuilder(mActivityUnderTest);
+ mActivityUnderTest.onCreateOptionsMenu(menu);
+ mActivityUnderTest.onPrepareOptionsMenu(menu);
+ assertTrue(menu.findItem(R.id.menu_remove_from_call_log).isVisible());
+ }
+
+ /**
+ * Test to show that we are correctly displaying playback rate on the ui.
+ * <p>
+ * See bug http://b/5044075.
+ */
+ @Suppress
+ public void testVoicemailPlaybackRateDisplayedOnUi() throws Throwable {
+ setActivityIntentForTestVoicemailEntry();
+ startActivityUnderTest();
+ // Find the TextView containing the duration. It should be initially displaying "00:00".
+ List<TextView> views = mTestUtils.getTextViewsWithString(mActivityUnderTest, "00:00");
+ assertEquals(1, views.size());
+ TextView timeDisplay = views.get(0);
+ // Hit the plus button. At this point we should be displaying "fast speed".
+ mTestUtils.clickButton(mActivityUnderTest, R.id.rate_increase_button);
+ assertEquals("fast speed", mTestUtils.getText(timeDisplay));
+ // Hit the minus button. We should be back to "normal" speed.
+ mTestUtils.clickButton(mActivityUnderTest, R.id.rate_decrease_button);
+ assertEquals("normal speed", mTestUtils.getText(timeDisplay));
+ // Wait for one and a half seconds. The timer will be back.
+ Thread.sleep(1500);
+ assertEquals("00:00", mTestUtils.getText(timeDisplay));
+ }
+
+ @Suppress
+ public void testClickingCallStopsPlayback() throws Throwable {
+ setActivityIntentForRealFileVoicemailEntry();
+ startActivityUnderTest();
+ mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
+ mFakeAsyncTaskExecutor.runTask(PREPARE_MEDIA_PLAYER);
+ mTestUtils.clickButton(mActivityUnderTest, R.id.playback_speakerphone);
+ mTestUtils.clickButton(mActivityUnderTest, R.id.playback_start_stop);
+ mTestUtils.clickButton(mActivityUnderTest, R.id.call_and_sms_main_action);
+ 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.
+ }
+
+ private void setActivityIntentForTestCallEntry() {
+ assertNull(mCallLogUri);
+ ContentResolver contentResolver = getContentResolver();
+ ContentValues values = new ContentValues();
+ values.put(CallLog.Calls.NUMBER, CONTACT_NUMBER);
+ values.put(CallLog.Calls.TYPE, CallLog.Calls.INCOMING_TYPE);
+ mCallLogUri = contentResolver.insert(CallLog.Calls.CONTENT_URI, values);
+ setActivityIntent(new Intent(Intent.ACTION_VIEW, mCallLogUri));
+ }
+
+ private void setActivityIntentForTestVoicemailEntry() {
+ 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);
+ 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);
+ }
+
+ 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();
+ OutputStream outputStream = null;
+ InputStream inputStream = null;
+ try {
+ inputStream = assets.open(TEST_ASSET_NAME);
+ outputStream = getContentResolver().openOutputStream(mVoicemailUri);
+ copyBetweenStreams(inputStream, outputStream);
+ } finally {
+ Closeables.closeQuietly(outputStream);
+ Closeables.closeQuietly(inputStream);
+ }
+ 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;
+ int total = 0;
+ while ((bytesRead = in.read(buffer)) != -1) {
+ total += bytesRead;
+ out.write(buffer, 0, bytesRead);
+ }
+ }
+
+ private void cleanUpUri() {
+ if (mVoicemailUri != null) {
+ getContentResolver().delete(VoicemailContract.Voicemails.CONTENT_URI,
+ "_ID = ?", new String[] { String.valueOf(ContentUris.parseId(mVoicemailUri)) });
+ mVoicemailUri = null;
+ }
+ if (mCallLogUri != null) {
+ getContentResolver().delete(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
+ "_ID = ?", new String[] { String.valueOf(ContentUris.parseId(mCallLogUri)) });
+ mCallLogUri = null;
+ }
+ }
+
+ private ContentResolver getContentResolver() {
+ 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 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(UPDATE_PHONE_CALL_DETAILS);
+ }
+
+ private AssetManager getAssets() {
+ return getInstrumentation().getContext().getAssets();
+ }
+}
diff --git a/tests/src/com/android/dialer/DialerLaunchPerformance.java b/tests/src/com/android/dialer/DialerLaunchPerformance.java
new file mode 100644
index 000000000..cf64f9449
--- /dev/null
+++ b/tests/src/com/android/dialer/DialerLaunchPerformance.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.test.LaunchPerformanceBase;
+
+/**
+ * Instrumentation class for Address Book launch performance testing.
+ */
+public class DialerLaunchPerformance extends LaunchPerformanceBase {
+
+ @Override
+ public void onCreate(Bundle arguments) {
+ mIntent.setAction(Intent.ACTION_MAIN);
+ mIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ mIntent.setComponent(new ComponentName("com.android.contacts",
+ "testcom.android.dialer.DialtactsActivity"));
+
+ start();
+ }
+
+ /**
+ * Calls LaunchApp and finish.
+ */
+ @Override
+ public void onStart() {
+ super.onStart();
+ LaunchApp();
+ finish(Activity.RESULT_OK, mResults);
+ }
+}
diff --git a/tests/src/com/android/dialer/PhoneCallDetailsHelperTest.java b/tests/src/com/android/dialer/PhoneCallDetailsHelperTest.java
new file mode 100644
index 000000000..961764475
--- /dev/null
+++ b/tests/src/com/android/dialer/PhoneCallDetailsHelperTest.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.CallLog.Calls;
+import android.test.AndroidTestCase;
+import android.text.Html;
+import android.text.Spanned;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.dialer.calllog.CallTypeHelper;
+import com.android.dialer.calllog.PhoneNumberHelper;
+import com.android.dialer.calllog.TestPhoneNumberHelper;
+import com.android.dialer.util.LocaleTestUtils;
+import com.android.internal.telephony.CallerInfo;
+
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
+/**
+ * Unit tests for {@link PhoneCallDetailsHelper}.
+ */
+public class PhoneCallDetailsHelperTest extends AndroidTestCase {
+ /** The number to be used to access the voicemail. */
+ private static final String TEST_VOICEMAIL_NUMBER = "125";
+ /** The date of the call log entry. */
+ private static final long TEST_DATE =
+ new GregorianCalendar(2011, 5, 3, 13, 0, 0).getTimeInMillis();
+ /** A test duration value for phone calls. */
+ private static final long TEST_DURATION = 62300;
+ /** The number of the caller/callee in the log entry. */
+ private static final String TEST_NUMBER = "14125555555";
+ /** The formatted version of {@link #TEST_NUMBER}. */
+ private static final String TEST_FORMATTED_NUMBER = "1-412-255-5555";
+ /** The country ISO name used in the tests. */
+ private static final String TEST_COUNTRY_ISO = "US";
+ /** The geocoded location used in the tests. */
+ private static final String TEST_GEOCODE = "United States";
+
+ /** The object under test. */
+ private PhoneCallDetailsHelper mHelper;
+ /** The views to fill. */
+ private PhoneCallDetailsViews mViews;
+ private TextView mNameView;
+ private PhoneNumberHelper mPhoneNumberHelper;
+ private LocaleTestUtils mLocaleTestUtils;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ Context context = getContext();
+ Resources resources = context.getResources();
+ CallTypeHelper callTypeHelper = new CallTypeHelper(resources);
+ mPhoneNumberHelper = new TestPhoneNumberHelper(resources, TEST_VOICEMAIL_NUMBER);
+ mHelper = new PhoneCallDetailsHelper(resources, callTypeHelper, mPhoneNumberHelper);
+ mHelper.setCurrentTimeForTest(
+ new GregorianCalendar(2011, 5, 4, 13, 0, 0).getTimeInMillis());
+ mViews = PhoneCallDetailsViews.createForTest(context);
+ mNameView = new TextView(context);
+ mLocaleTestUtils = new LocaleTestUtils(getContext());
+ mLocaleTestUtils.setLocale(Locale.US);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mLocaleTestUtils.restoreLocale();
+ mNameView = null;
+ mViews = null;
+ mHelper = null;
+ mPhoneNumberHelper = null;
+ super.tearDown();
+ }
+
+ public void testSetPhoneCallDetails_Unknown() {
+ setPhoneCallDetailsWithNumber(CallerInfo.UNKNOWN_NUMBER, CallerInfo.UNKNOWN_NUMBER);
+ assertNameEqualsResource(R.string.unknown);
+ }
+
+ public void testSetPhoneCallDetails_Private() {
+ setPhoneCallDetailsWithNumber(CallerInfo.PRIVATE_NUMBER, CallerInfo.PRIVATE_NUMBER);
+ assertNameEqualsResource(R.string.private_num);
+ }
+
+ public void testSetPhoneCallDetails_Payphone() {
+ setPhoneCallDetailsWithNumber(CallerInfo.PAYPHONE_NUMBER, CallerInfo.PAYPHONE_NUMBER);
+ assertNameEqualsResource(R.string.payphone);
+ }
+
+ public void testSetPhoneCallDetails_Voicemail() {
+ setPhoneCallDetailsWithNumber(TEST_VOICEMAIL_NUMBER, TEST_VOICEMAIL_NUMBER);
+ assertNameEqualsResource(R.string.voicemail);
+ }
+
+ public void testSetPhoneCallDetails_Normal() {
+ setPhoneCallDetailsWithNumber("14125551212", "1-412-555-1212");
+ assertEquals("yesterday", mViews.callTypeAndDate.getText().toString());
+ assertEqualsHtml("<font color='#33b5e5'><b>yesterday</b></font>",
+ mViews.callTypeAndDate.getText());
+ }
+
+ /** Asserts that a char sequence is actually a Spanned corresponding to the expected HTML. */
+ private void assertEqualsHtml(String expectedHtml, CharSequence actualText) {
+ // In order to contain HTML, the text should actually be a Spanned.
+ assertTrue(actualText instanceof Spanned);
+ Spanned actualSpanned = (Spanned) actualText;
+ // Convert from and to HTML to take care of alternative formatting of HTML.
+ assertEquals(Html.toHtml(Html.fromHtml(expectedHtml)), Html.toHtml(actualSpanned));
+
+ }
+
+ public void testSetPhoneCallDetails_Date() {
+ mHelper.setCurrentTimeForTest(
+ new GregorianCalendar(2011, 5, 3, 13, 0, 0).getTimeInMillis());
+
+ setPhoneCallDetailsWithDate(
+ new GregorianCalendar(2011, 5, 3, 13, 0, 0).getTimeInMillis());
+ assertDateEquals("0 mins ago");
+
+ setPhoneCallDetailsWithDate(
+ new GregorianCalendar(2011, 5, 3, 12, 0, 0).getTimeInMillis());
+ assertDateEquals("1 hour ago");
+
+ setPhoneCallDetailsWithDate(
+ new GregorianCalendar(2011, 5, 2, 13, 0, 0).getTimeInMillis());
+ assertDateEquals("yesterday");
+
+ setPhoneCallDetailsWithDate(
+ new GregorianCalendar(2011, 5, 1, 13, 0, 0).getTimeInMillis());
+ assertDateEquals("2 days ago");
+ }
+
+ public void testSetPhoneCallDetails_CallTypeIcons() {
+ setPhoneCallDetailsWithCallTypeIcons(Calls.INCOMING_TYPE);
+ assertCallTypeIconsEquals(Calls.INCOMING_TYPE);
+
+ setPhoneCallDetailsWithCallTypeIcons(Calls.OUTGOING_TYPE);
+ assertCallTypeIconsEquals(Calls.OUTGOING_TYPE);
+
+ setPhoneCallDetailsWithCallTypeIcons(Calls.MISSED_TYPE);
+ assertCallTypeIconsEquals(Calls.MISSED_TYPE);
+
+ setPhoneCallDetailsWithCallTypeIcons(Calls.VOICEMAIL_TYPE);
+ assertCallTypeIconsEquals(Calls.VOICEMAIL_TYPE);
+ }
+
+ public void testSetPhoneCallDetails_MultipleCallTypeIcons() {
+ setPhoneCallDetailsWithCallTypeIcons(Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
+ assertCallTypeIconsEquals(Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
+
+ setPhoneCallDetailsWithCallTypeIcons(Calls.MISSED_TYPE, Calls.MISSED_TYPE);
+ assertCallTypeIconsEquals(Calls.MISSED_TYPE, Calls.MISSED_TYPE);
+ }
+
+ public void testSetPhoneCallDetails_MultipleCallTypeIconsLastOneDropped() {
+ setPhoneCallDetailsWithCallTypeIcons(Calls.MISSED_TYPE, Calls.MISSED_TYPE,
+ Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
+ assertCallTypeIconsEqualsPlusOverflow("(4)",
+ Calls.MISSED_TYPE, Calls.MISSED_TYPE, Calls.INCOMING_TYPE);
+ }
+
+ public void testSetPhoneCallDetails_Geocode() {
+ setPhoneCallDetailsWithNumberAndGeocode("+14125555555", "1-412-555-5555", "Pennsylvania");
+ assertNameEquals("1-412-555-5555"); // The phone number is shown as the name.
+ assertNumberEquals("Pennsylvania"); // The geocode is shown as the number.
+ }
+
+ public void testSetPhoneCallDetails_NoGeocode() {
+ setPhoneCallDetailsWithNumberAndGeocode("+14125555555", "1-412-555-5555", null);
+ assertNameEquals("1-412-555-5555"); // The phone number is shown as the name.
+ assertNumberEquals("-"); // The empty geocode is shown as the number.
+ }
+
+ public void testSetPhoneCallDetails_EmptyGeocode() {
+ setPhoneCallDetailsWithNumberAndGeocode("+14125555555", "1-412-555-5555", "");
+ assertNameEquals("1-412-555-5555"); // The phone number is shown as the name.
+ assertNumberEquals("-"); // The empty geocode is shown as the number.
+ }
+
+ public void testSetPhoneCallDetails_NoGeocodeForVoicemail() {
+ setPhoneCallDetailsWithNumberAndGeocode(TEST_VOICEMAIL_NUMBER, "", "United States");
+ assertNumberEquals("-"); // The empty geocode is shown as the number.
+ }
+
+ public void testSetPhoneCallDetails_Highlighted() {
+ setPhoneCallDetailsWithNumber(TEST_VOICEMAIL_NUMBER, "");
+ }
+
+ public void testSetCallDetailsHeader_NumberOnly() {
+ setCallDetailsHeaderWithNumberOnly(TEST_NUMBER);
+ assertEquals(View.VISIBLE, mNameView.getVisibility());
+ assertEquals("Add to contacts", mNameView.getText().toString());
+ }
+
+ public void testSetCallDetailsHeader_UnknownNumber() {
+ setCallDetailsHeaderWithNumberOnly(CallerInfo.UNKNOWN_NUMBER);
+ assertEquals(View.VISIBLE, mNameView.getVisibility());
+ assertEquals("Unknown", mNameView.getText().toString());
+ }
+
+ public void testSetCallDetailsHeader_PrivateNumber() {
+ setCallDetailsHeaderWithNumberOnly(CallerInfo.PRIVATE_NUMBER);
+ assertEquals(View.VISIBLE, mNameView.getVisibility());
+ assertEquals("Private number", mNameView.getText().toString());
+ }
+
+ public void testSetCallDetailsHeader_PayphoneNumber() {
+ setCallDetailsHeaderWithNumberOnly(CallerInfo.PAYPHONE_NUMBER);
+ assertEquals(View.VISIBLE, mNameView.getVisibility());
+ assertEquals("Pay phone", mNameView.getText().toString());
+ }
+
+ public void testSetCallDetailsHeader_VoicemailNumber() {
+ setCallDetailsHeaderWithNumberOnly(TEST_VOICEMAIL_NUMBER);
+ assertEquals(View.VISIBLE, mNameView.getVisibility());
+ assertEquals("Voicemail", mNameView.getText().toString());
+ }
+
+ public void testSetCallDetailsHeader() {
+ setCallDetailsHeader("John Doe");
+ assertEquals(View.VISIBLE, mNameView.getVisibility());
+ assertEquals("John Doe", mNameView.getText().toString());
+ }
+
+ /** Asserts that the name text field contains the value of the given string resource. */
+ private void assertNameEqualsResource(int resId) {
+ assertNameEquals(getContext().getString(resId));
+ }
+
+ /** Asserts that the name text field contains the given string value. */
+ private void assertNameEquals(String text) {
+ assertEquals(text, mViews.nameView.getText().toString());
+ }
+
+ /** Asserts that the number text field contains the given string value. */
+ private void assertNumberEquals(String text) {
+ assertEquals(text, mViews.numberView.getText().toString());
+ }
+
+ /** Asserts that the date text field contains the given string value. */
+ private void assertDateEquals(String text) {
+ assertEquals(text, mViews.callTypeAndDate.getText().toString());
+ }
+
+ /** Asserts that the call type contains the images with the given drawables. */
+ private void assertCallTypeIconsEquals(int... ids) {
+ assertEquals(ids.length, mViews.callTypeIcons.getCount());
+ for (int index = 0; index < ids.length; ++index) {
+ int id = ids[index];
+ assertEquals(id, mViews.callTypeIcons.getCallType(index));
+ }
+ assertEquals(View.VISIBLE, mViews.callTypeIcons.getVisibility());
+ assertEquals("yesterday", mViews.callTypeAndDate.getText().toString());
+ }
+
+ /**
+ * Asserts that the call type contains the images with the given drawables and shows the given
+ * text next to the icons.
+ */
+ private void assertCallTypeIconsEqualsPlusOverflow(String overflowText, int... ids) {
+ assertEquals(ids.length, mViews.callTypeIcons.getCount());
+ for (int index = 0; index < ids.length; ++index) {
+ int id = ids[index];
+ assertEquals(id, mViews.callTypeIcons.getCallType(index));
+ }
+ assertEquals(View.VISIBLE, mViews.callTypeIcons.getVisibility());
+ assertEquals(overflowText + " yesterday", mViews.callTypeAndDate.getText().toString());
+ }
+
+ /** Sets the phone call details with default values and the given number. */
+ private void setPhoneCallDetailsWithNumber(String number, String formattedNumber) {
+ setPhoneCallDetailsWithNumberAndGeocode(number, formattedNumber, TEST_GEOCODE);
+ }
+
+ /** Sets the phone call details with default values and the given number. */
+ private void setPhoneCallDetailsWithNumberAndGeocode(String number, String formattedNumber,
+ String geocodedLocation) {
+ mHelper.setPhoneCallDetails(mViews,
+ new PhoneCallDetails(number, formattedNumber, TEST_COUNTRY_ISO, geocodedLocation,
+ new int[]{ Calls.VOICEMAIL_TYPE }, TEST_DATE, TEST_DURATION),
+ true);
+ }
+
+ /** Sets the phone call details with default values and the given date. */
+ private void setPhoneCallDetailsWithDate(long date) {
+ mHelper.setPhoneCallDetails(mViews,
+ new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
+ TEST_GEOCODE, new int[]{ Calls.INCOMING_TYPE }, date, TEST_DURATION),
+ false);
+ }
+
+ /** Sets the phone call details with default values and the given call types using icons. */
+ private void setPhoneCallDetailsWithCallTypeIcons(int... callTypes) {
+ mHelper.setPhoneCallDetails(mViews,
+ new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
+ TEST_GEOCODE, callTypes, TEST_DATE, TEST_DURATION),
+ false);
+ }
+
+ private void setCallDetailsHeaderWithNumberOnly(String number) {
+ mHelper.setCallDetailsHeader(mNameView,
+ new PhoneCallDetails(number, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
+ TEST_GEOCODE, new int[]{ Calls.INCOMING_TYPE }, TEST_DATE, TEST_DURATION));
+ }
+
+ private void setCallDetailsHeader(String name) {
+ mHelper.setCallDetailsHeader(mNameView,
+ new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
+ TEST_GEOCODE, new int[]{ Calls.INCOMING_TYPE }, TEST_DATE, TEST_DURATION,
+ name, 0, "", null, null));
+ }
+}
diff --git a/tests/src/com/android/dialer/calllog/CallLogAdapterTest.java b/tests/src/com/android/dialer/calllog/CallLogAdapterTest.java
new file mode 100644
index 000000000..6ec3e76ef
--- /dev/null
+++ b/tests/src/com/android/dialer/calllog/CallLogAdapterTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.calllog;
+
+import android.content.Context;
+import android.database.MatrixCursor;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Unit tests for {@link CallLogAdapter}.
+ */
+@SmallTest
+public class CallLogAdapterTest extends AndroidTestCase {
+ private static final String TEST_NUMBER = "12345678";
+ private static final String TEST_NAME = "name";
+ private static final String TEST_NUMBER_LABEL = "label";
+ private static final int TEST_NUMBER_TYPE = 1;
+ private static final String TEST_COUNTRY_ISO = "US";
+
+ /** The object under test. */
+ private TestCallLogAdapter mAdapter;
+
+ private MatrixCursor mCursor;
+ private View mView;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // Use a call fetcher that does not do anything.
+ CallLogAdapter.CallFetcher fakeCallFetcher = new CallLogAdapter.CallFetcher() {
+ @Override
+ public void fetchCalls() {}
+ };
+
+ ContactInfoHelper fakeContactInfoHelper =
+ new ContactInfoHelper(getContext(), TEST_COUNTRY_ISO) {
+ @Override
+ public ContactInfo lookupNumber(String number, String countryIso) {
+ ContactInfo info = new ContactInfo();
+ info.number = number;
+ info.formattedNumber = number;
+ return info;
+ }
+ };
+
+ mAdapter = new TestCallLogAdapter(getContext(), fakeCallFetcher, fakeContactInfoHelper);
+ // The cursor used in the tests to store the entries to display.
+ mCursor = new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
+ mCursor.moveToFirst();
+ // The views into which to store the data.
+ mView = new View(getContext());
+ mView.setTag(CallLogListItemViews.createForTest(getContext()));
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mAdapter = null;
+ mCursor = null;
+ mView = null;
+ super.tearDown();
+ }
+
+ public void testBindView_NoCallLogCacheNorMemoryCache_EnqueueRequest() {
+ mCursor.addRow(createCallLogEntry());
+
+ // Bind the views of a single row.
+ mAdapter.bindStandAloneView(mView, getContext(), mCursor);
+
+ // There is one request for contact details.
+ assertEquals(1, mAdapter.requests.size());
+
+ TestCallLogAdapter.Request request = mAdapter.requests.get(0);
+ // It is for the number we need to show.
+ assertEquals(TEST_NUMBER, request.number);
+ // It has the right country.
+ assertEquals(TEST_COUNTRY_ISO, request.countryIso);
+ // Since there is nothing in the cache, it is an immediate request.
+ assertTrue("should be immediate", request.immediate);
+ }
+
+ public void testBindView_CallLogCacheButNoMemoryCache_EnqueueRequest() {
+ mCursor.addRow(createCallLogEntryWithCachedValues());
+
+ // Bind the views of a single row.
+ mAdapter.bindStandAloneView(mView, getContext(), mCursor);
+
+ // There is one request for contact details.
+ assertEquals(1, mAdapter.requests.size());
+
+ TestCallLogAdapter.Request request = mAdapter.requests.get(0);
+ // The values passed to the request, match the ones in the call log cache.
+ assertEquals(TEST_NAME, request.callLogInfo.name);
+ assertEquals(1, request.callLogInfo.type);
+ assertEquals(TEST_NUMBER_LABEL, request.callLogInfo.label);
+ }
+
+
+ public void testBindView_NoCallLogButMemoryCache_EnqueueRequest() {
+ mCursor.addRow(createCallLogEntry());
+ mAdapter.injectContactInfoForTest(TEST_NUMBER, TEST_COUNTRY_ISO, createContactInfo());
+
+ // Bind the views of a single row.
+ mAdapter.bindStandAloneView(mView, getContext(), mCursor);
+
+ // There is one request for contact details.
+ assertEquals(1, mAdapter.requests.size());
+
+ TestCallLogAdapter.Request request = mAdapter.requests.get(0);
+ // Since there is something in the cache, it is not an immediate request.
+ assertFalse("should not be immediate", request.immediate);
+ }
+
+ public void testBindView_BothCallLogAndMemoryCache_NoEnqueueRequest() {
+ mCursor.addRow(createCallLogEntryWithCachedValues());
+ mAdapter.injectContactInfoForTest(TEST_NUMBER, TEST_COUNTRY_ISO, createContactInfo());
+
+ // Bind the views of a single row.
+ mAdapter.bindStandAloneView(mView, getContext(), mCursor);
+
+ // Cache and call log are up-to-date: no need to request update.
+ assertEquals(0, mAdapter.requests.size());
+ }
+
+ public void testBindView_MismatchBetwenCallLogAndMemoryCache_EnqueueRequest() {
+ mCursor.addRow(createCallLogEntryWithCachedValues());
+
+ // Contact info contains a different name.
+ ContactInfo info = createContactInfo();
+ info.name = "new name";
+ mAdapter.injectContactInfoForTest(TEST_NUMBER, TEST_COUNTRY_ISO, info);
+
+ // Bind the views of a single row.
+ mAdapter.bindStandAloneView(mView, getContext(), mCursor);
+
+ // There is one request for contact details.
+ assertEquals(1, mAdapter.requests.size());
+
+ TestCallLogAdapter.Request request = mAdapter.requests.get(0);
+ // Since there is something in the cache, it is not an immediate request.
+ assertFalse("should not be immediate", request.immediate);
+ }
+
+ /** Returns a contact info with default values. */
+ private ContactInfo createContactInfo() {
+ ContactInfo info = new ContactInfo();
+ info.number = TEST_NUMBER;
+ info.name = TEST_NAME;
+ info.type = TEST_NUMBER_TYPE;
+ info.label = TEST_NUMBER_LABEL;
+ return info;
+ }
+
+ /** Returns a call log entry without cached values. */
+ private Object[] createCallLogEntry() {
+ Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
+ values[CallLogQuery.NUMBER] = TEST_NUMBER;
+ values[CallLogQuery.COUNTRY_ISO] = TEST_COUNTRY_ISO;
+ return values;
+ }
+
+ /** Returns a call log entry with a cached values. */
+ private Object[] createCallLogEntryWithCachedValues() {
+ Object[] values = createCallLogEntry();
+ values[CallLogQuery.CACHED_NAME] = TEST_NAME;
+ values[CallLogQuery.CACHED_NUMBER_TYPE] = TEST_NUMBER_TYPE;
+ values[CallLogQuery.CACHED_NUMBER_LABEL] = TEST_NUMBER_LABEL;
+ return values;
+ }
+
+ /**
+ * Subclass of {@link CallLogAdapter} used in tests to intercept certain calls.
+ */
+ // TODO: This would be better done by splitting the contact lookup into a collaborator class
+ // instead.
+ private static final class TestCallLogAdapter extends CallLogAdapter {
+ public static class Request {
+ public final String number;
+ public final String countryIso;
+ public final ContactInfo callLogInfo;
+ public final boolean immediate;
+
+ public Request(String number, String countryIso, ContactInfo callLogInfo,
+ boolean immediate) {
+ this.number = number;
+ this.countryIso = countryIso;
+ this.callLogInfo = callLogInfo;
+ this.immediate = immediate;
+ }
+ }
+
+ public final List<Request> requests = Lists.newArrayList();
+
+ public TestCallLogAdapter(Context context, CallFetcher callFetcher,
+ ContactInfoHelper contactInfoHelper) {
+ super(context, callFetcher, contactInfoHelper);
+ }
+
+ @Override
+ void enqueueRequest(String number, String countryIso, ContactInfo callLogInfo,
+ boolean immediate) {
+ requests.add(new Request(number, countryIso, callLogInfo, immediate));
+ }
+ }
+}
diff --git a/tests/src/com/android/dialer/calllog/CallLogFragmentTest.java b/tests/src/com/android/dialer/calllog/CallLogFragmentTest.java
new file mode 100644
index 000000000..f4534320f
--- /dev/null
+++ b/tests/src/com/android/dialer/calllog/CallLogFragmentTest.java
@@ -0,0 +1,632 @@
+/*
+ * Copyright (C) 2009 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.calllog;
+
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.ComponentName;
+import android.content.ContentUris;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.MatrixCursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.VoicemailContract;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.dialer.CallDetailActivity;
+import com.android.contacts.R;
+import com.android.contacts.common.test.FragmentTestActivity;
+import com.android.internal.telephony.CallerInfo;
+
+import java.util.Date;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.Random;
+
+/**
+ * Tests for the contact call list activity.
+ *
+ * Running all tests:
+ *
+ * runtest contacts
+ * or
+ * adb shell am instrument \
+ * -w com.android.contacts.tests/android.test.InstrumentationTestRunner
+ */
+@LargeTest
+public class CallLogFragmentTest extends ActivityInstrumentationTestCase2<FragmentTestActivity> {
+ private static final int RAND_DURATION = -1;
+ private static final long NOW = -1L;
+
+ /** A test value for the URI of a contact. */
+ private static final Uri TEST_LOOKUP_URI = Uri.parse("content://contacts/2");
+ /** A test value for the country ISO of the phone number in the call log. */
+ private static final String TEST_COUNTRY_ISO = "US";
+ /** A phone number to be used in tests. */
+ private static final String TEST_NUMBER = "12125551000";
+ /** The formatted version of {@link #TEST_NUMBER}. */
+ private static final String TEST_FORMATTED_NUMBER = "1 212-555-1000";
+
+ /** The activity in which we are hosting the fragment. */
+ private FragmentTestActivity mActivity;
+ private CallLogFragment mFragment;
+ private FrameLayout mParentView;
+ /**
+ * The adapter used by the fragment to build the rows in the call log. We use it with our own in
+ * memory database.
+ */
+ private CallLogAdapter mAdapter;
+ private String mVoicemail;
+
+ // In memory array to hold the rows corresponding to the 'calls' table.
+ private MatrixCursor mCursor;
+ private int mIndex; // Of the next row.
+
+ private Random mRnd;
+
+ // References to the icons bitmaps used to build the list are stored in a
+ // map mIcons. The keys to retrieve the icons are:
+ // Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE and Calls.MISSED_TYPE.
+ private HashMap<Integer, Bitmap> mCallTypeIcons;
+
+ // An item in the call list. All the methods performing checks use it.
+ private CallLogListItemViews mItem;
+ // The list of views representing the data in the DB. View are in
+ // reverse order compare to the DB.
+ private View[] mList;
+
+ public CallLogFragmentTest() {
+ super("com.android.dialer", FragmentTestActivity.class);
+ mIndex = 1;
+ mRnd = new Random();
+ }
+
+ @Override
+ public void setUp() {
+ mActivity = getActivity();
+ // Needed by the CallLogFragment.
+ mActivity.setTheme(R.style.DialtactsTheme);
+
+ // Create the fragment and load it into the activity.
+ mFragment = new CallLogFragment();
+ FragmentManager fragmentManager = mActivity.getFragmentManager();
+ FragmentTransaction transaction = fragmentManager.beginTransaction();
+ transaction.add(FragmentTestActivity.LAYOUT_ID, mFragment);
+ transaction.commit();
+ // Wait for the fragment to be loaded.
+ getInstrumentation().waitForIdleSync();
+
+ mVoicemail = TelephonyManager.getDefault().getVoiceMailNumber();
+ mAdapter = mFragment.getAdapter();
+ // Do not process requests for details during tests. This would start a background thread,
+ // which makes the tests flaky.
+ mAdapter.disableRequestProcessingForTest();
+ mAdapter.stopRequestProcessing();
+ mParentView = new FrameLayout(mActivity);
+ mCursor = new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
+ buildIconMap();
+ }
+
+ /**
+ * Checks that the call icon is not visible for private and
+ * unknown numbers.
+ * Use 2 passes, one where new views are created and one where
+ * half of the total views are updated and the other half created.
+ */
+ @MediumTest
+ public void testCallViewIsNotVisibleForPrivateAndUnknownNumbers() {
+ final int SIZE = 100;
+ mList = new View[SIZE];
+
+ // Insert the first batch of entries.
+ mCursor.moveToFirst();
+ insertRandomEntries(SIZE / 2);
+ int startOfSecondBatch = mCursor.getPosition();
+
+ buildViewListFromDb();
+ checkCallStatus();
+
+ // Append the rest of the entries. We keep the first set of
+ // views around so they get updated and not built from
+ // scratch, this exposes some bugs that are not there when the
+ // call log is launched for the 1st time but show up when the
+ // call log gets updated afterwards.
+ mCursor.move(startOfSecondBatch);
+ insertRandomEntries(SIZE / 2);
+
+ buildViewListFromDb();
+ checkCallStatus();
+ }
+
+ @MediumTest
+ public void testCallAndGroupViews_GroupView() {
+ mCursor.moveToFirst();
+ insert(CallerInfo.PRIVATE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
+ insert(CallerInfo.PRIVATE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
+ insert(CallerInfo.PRIVATE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
+ View view = mAdapter.newGroupView(getActivity(), mParentView);
+ mAdapter.bindGroupView(view, getActivity(), mCursor, 3, false);
+ assertNotNull(view.findViewById(R.id.secondary_action_icon));
+ }
+
+ @MediumTest
+ public void testCallAndGroupViews_StandAloneView() {
+ mCursor.moveToFirst();
+ insert(CallerInfo.PRIVATE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
+ View view = mAdapter.newStandAloneView(getActivity(), mParentView);
+ mAdapter.bindStandAloneView(view, getActivity(), mCursor);
+ assertNotNull(view.findViewById(R.id.secondary_action_icon));
+ }
+
+ @MediumTest
+ public void testCallAndGroupViews_ChildView() {
+ mCursor.moveToFirst();
+ insert(CallerInfo.PRIVATE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
+ View view = mAdapter.newChildView(getActivity(), mParentView);
+ mAdapter.bindChildView(view, getActivity(), mCursor);
+ assertNotNull(view.findViewById(R.id.secondary_action_icon));
+ }
+
+ @MediumTest
+ public void testBindView_NumberOnlyNoCache() {
+ mCursor.moveToFirst();
+ insert(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
+ View view = mAdapter.newStandAloneView(getActivity(), mParentView);
+ mAdapter.bindStandAloneView(view, getActivity(), mCursor);
+
+ CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+ assertNameIs(views, TEST_NUMBER);
+ }
+
+ @MediumTest
+ public void testBindView_NumberOnlyDbCachedFormattedNumber() {
+ mCursor.moveToFirst();
+ Object[] values = getValuesToInsert(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
+ values[CallLogQuery.CACHED_FORMATTED_NUMBER] = TEST_FORMATTED_NUMBER;
+ insertValues(values);
+ View view = mAdapter.newStandAloneView(getActivity(), mParentView);
+ mAdapter.bindStandAloneView(view, getActivity(), mCursor);
+
+ CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+ assertNameIs(views, TEST_FORMATTED_NUMBER);
+ }
+
+ @MediumTest
+ public void testBindView_WithCachedName() {
+ mCursor.moveToFirst();
+ insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
+ "John Doe", Phone.TYPE_HOME, "");
+ View view = mAdapter.newStandAloneView(getActivity(), mParentView);
+ mAdapter.bindStandAloneView(view, getActivity(), mCursor);
+
+ CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+ assertNameIs(views, "John Doe");
+ assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, getTypeLabel(Phone.TYPE_HOME));
+ }
+
+ @MediumTest
+ public void testBindView_UriNumber() {
+ mCursor.moveToFirst();
+ insertWithCachedValues("sip:johndoe@gmail.com", NOW, 0, Calls.INCOMING_TYPE,
+ "John Doe", Phone.TYPE_HOME, "");
+ View view = mAdapter.newStandAloneView(getActivity(), mParentView);
+ mAdapter.bindStandAloneView(view, getActivity(), mCursor);
+
+ CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+ assertNameIs(views, "John Doe");
+ assertNumberAndLabelAre(views, "sip:johndoe@gmail.com", null);
+ }
+
+ @MediumTest
+ public void testBindView_HomeLabel() {
+ mCursor.moveToFirst();
+ insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
+ "John Doe", Phone.TYPE_HOME, "");
+ View view = mAdapter.newStandAloneView(getActivity(), mParentView);
+ mAdapter.bindStandAloneView(view, getActivity(), mCursor);
+
+ CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+ assertNameIs(views, "John Doe");
+ assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, getTypeLabel(Phone.TYPE_HOME));
+ }
+
+ @MediumTest
+ public void testBindView_WorkLabel() {
+ mCursor.moveToFirst();
+ insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
+ "John Doe", Phone.TYPE_WORK, "");
+ View view = mAdapter.newStandAloneView(getActivity(), mParentView);
+ mAdapter.bindStandAloneView(view, getActivity(), mCursor);
+
+ CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+ assertNameIs(views, "John Doe");
+ assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, getTypeLabel(Phone.TYPE_WORK));
+ }
+
+ @MediumTest
+ public void testBindView_CustomLabel() {
+ mCursor.moveToFirst();
+ String numberLabel = "My label";
+ insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
+ "John Doe", Phone.TYPE_CUSTOM, numberLabel);
+ View view = mAdapter.newStandAloneView(getActivity(), mParentView);
+ mAdapter.bindStandAloneView(view, getActivity(), mCursor);
+
+ CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+ assertNameIs(views, "John Doe");
+ assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, numberLabel);
+ }
+
+ @MediumTest
+ public void testBindView_WithQuickContactBadge() {
+ mCursor.moveToFirst();
+ insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
+ "John Doe", Phone.TYPE_HOME, "");
+ View view = mAdapter.newStandAloneView(getActivity(), mParentView);
+ mAdapter.bindStandAloneView(view, getActivity(), mCursor);
+
+ CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+ assertTrue(views.quickContactView.isEnabled());
+ }
+
+ @MediumTest
+ public void testBindView_WithoutQuickContactBadge() {
+ mCursor.moveToFirst();
+ insert(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
+ View view = mAdapter.newStandAloneView(getActivity(), mParentView);
+ mAdapter.bindStandAloneView(view, getActivity(), mCursor);
+
+ CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+ assertFalse(views.quickContactView.isEnabled());
+ }
+
+ @MediumTest
+ public void testBindView_CallButton() {
+ mCursor.moveToFirst();
+ insert(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
+ View view = mAdapter.newStandAloneView(getActivity(), mParentView);
+ mAdapter.bindStandAloneView(view, getActivity(), mCursor);
+
+ CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+ IntentProvider intentProvider = (IntentProvider) views.secondaryActionView.getTag();
+ Intent intent = intentProvider.getIntent(mActivity);
+ // Starts a call.
+ assertEquals(Intent.ACTION_CALL_PRIVILEGED, intent.getAction());
+ // To the entry's number.
+ assertEquals(Uri.parse("tel:" + TEST_NUMBER), intent.getData());
+ }
+
+ @MediumTest
+ public void testBindView_PlayButton() {
+ mCursor.moveToFirst();
+ insertVoicemail(TEST_NUMBER, NOW, 0);
+ View view = mAdapter.newStandAloneView(getActivity(), mParentView);
+ mAdapter.bindStandAloneView(view, getActivity(), mCursor);
+
+ CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+ IntentProvider intentProvider = (IntentProvider) views.secondaryActionView.getTag();
+ Intent intent = intentProvider.getIntent(mActivity);
+ // Starts the call detail activity.
+ assertEquals(new ComponentName(mActivity, CallDetailActivity.class),
+ intent.getComponent());
+ // With the given entry.
+ assertEquals(ContentUris.withAppendedId(Calls.CONTENT_URI_WITH_VOICEMAIL, 1),
+ intent.getData());
+ // With the URI of the voicemail.
+ assertEquals(
+ ContentUris.withAppendedId(VoicemailContract.Voicemails.CONTENT_URI, 1),
+ intent.getParcelableExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI));
+ // And starts playback.
+ assertTrue(
+ intent.getBooleanExtra(CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK, false));
+ }
+
+ /** Returns the label associated with a given phone type. */
+ private CharSequence getTypeLabel(int phoneType) {
+ return Phone.getTypeLabel(getActivity().getResources(), phoneType, "");
+ }
+
+ //
+ // HELPERS to check conditions on the DB/views
+ //
+ /**
+ * Go over all the views in the list and check that the Call
+ * icon's visibility matches the nature of the number.
+ */
+ private void checkCallStatus() {
+ for (int i = 0; i < mList.length; i++) {
+ if (null == mList[i]) {
+ break;
+ }
+ mItem = (CallLogListItemViews) mList[i].getTag();
+ String number = getPhoneNumberForListEntry(i);
+ if (CallerInfo.PRIVATE_NUMBER.equals(number) ||
+ CallerInfo.UNKNOWN_NUMBER.equals(number)) {
+ assertFalse(View.VISIBLE == mItem.secondaryActionView.getVisibility());
+ } else {
+ assertEquals(View.VISIBLE, mItem.secondaryActionView.getVisibility());
+ }
+ }
+ }
+
+
+ //
+ // HELPERS to setup the tests.
+ //
+
+ /**
+ * Get the Bitmap from the icons in the contacts package.
+ */
+ private Bitmap getBitmap(String resName) {
+ Resources r = mActivity.getResources();
+ int resid = r.getIdentifier(resName, "drawable", "com.android.dialer");
+ BitmapDrawable d = (BitmapDrawable) r.getDrawable(resid);
+ assertNotNull(d);
+ return d.getBitmap();
+ }
+
+ /**
+ * Fetch all the icons we need in tests from the contacts app and store them in a map.
+ */
+ private void buildIconMap() {
+ mCallTypeIcons = new HashMap<Integer, Bitmap>(3);
+
+ mCallTypeIcons.put(Calls.INCOMING_TYPE, getBitmap("ic_call_incoming_holo_dark"));
+ mCallTypeIcons.put(Calls.MISSED_TYPE, getBitmap("ic_call_missed_holo_dark"));
+ mCallTypeIcons.put(Calls.OUTGOING_TYPE, getBitmap("ic_call_outgoing_holo_dark"));
+ }
+
+ //
+ // HELPERS to build/update the call entries (views) from the DB.
+ //
+
+ /**
+ * Read the DB and foreach call either update the existing view if
+ * one exists already otherwise create one.
+ * The list is build from a DESC view of the DB (last inserted entry is first).
+ */
+ private void buildViewListFromDb() {
+ int i = 0;
+ mCursor.moveToLast();
+ while(!mCursor.isBeforeFirst()) {
+ if (null == mList[i]) {
+ mList[i] = mAdapter.newStandAloneView(mActivity, mParentView);
+ }
+ mAdapter.bindStandAloneView(mList[i], mActivity, mCursor);
+ mCursor.moveToPrevious();
+ i++;
+ }
+ }
+
+ /** Returns the number associated with the given entry in {{@link #mList}. */
+ private String getPhoneNumberForListEntry(int index) {
+ // The entries are added backward, so count from the end of the cursor.
+ mCursor.moveToPosition(mCursor.getCount() - index - 1);
+ return mCursor.getString(CallLogQuery.NUMBER);
+ }
+
+ //
+ // HELPERS to insert numbers in the call log DB.
+ //
+
+ /**
+ * Insert a certain number of random numbers in the DB. Makes sure
+ * there is at least one private and one unknown number in the DB.
+ * @param num Of entries to be inserted.
+ */
+ private void insertRandomEntries(int num) {
+ if (num < 10) {
+ throw new IllegalArgumentException("num should be >= 10");
+ }
+ boolean privateOrUnknownOrVm[];
+ privateOrUnknownOrVm = insertRandomRange(0, num - 2);
+
+ if (privateOrUnknownOrVm[0] && privateOrUnknownOrVm[1]) {
+ insertRandomRange(num - 2, num);
+ } else {
+ insertPrivate(NOW, RAND_DURATION);
+ insertUnknown(NOW, RAND_DURATION);
+ }
+ }
+
+ /**
+ * Insert a new call entry in the test DB.
+ *
+ * It includes the values for the cached contact associated with the number.
+ *
+ * @param number The phone number. For unknown and private numbers,
+ * use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
+ * @param date In millisec since epoch. Use NOW to use the current time.
+ * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+ * @param type Either Call.OUTGOING_TYPE or Call.INCOMING_TYPE or Call.MISSED_TYPE.
+ * @param cachedName the name of the contact with this number
+ * @param cachedNumberType the type of the number, from the contact with this number
+ * @param cachedNumberLabel the label of the number, from the contact with this number
+ */
+ private void insertWithCachedValues(String number, long date, int duration, int type,
+ String cachedName, int cachedNumberType, String cachedNumberLabel) {
+ insert(number, date, duration, type);
+ ContactInfo contactInfo = new ContactInfo();
+ contactInfo.lookupUri = TEST_LOOKUP_URI;
+ contactInfo.name = cachedName;
+ contactInfo.type = cachedNumberType;
+ contactInfo.label = cachedNumberLabel;
+ String formattedNumber = PhoneNumberUtils.formatNumber(number, TEST_COUNTRY_ISO);
+ if (formattedNumber == null) {
+ formattedNumber = number;
+ }
+ contactInfo.formattedNumber = formattedNumber;
+ contactInfo.normalizedNumber = number;
+ contactInfo.photoId = 0;
+ mAdapter.injectContactInfoForTest(number, TEST_COUNTRY_ISO, contactInfo);
+ }
+
+ /**
+ * Insert a new call entry in the test DB.
+ * @param number The phone number. For unknown and private numbers,
+ * use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
+ * @param date In millisec since epoch. Use NOW to use the current time.
+ * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+ * @param type Either Call.OUTGOING_TYPE or Call.INCOMING_TYPE or Call.MISSED_TYPE.
+ */
+ private void insert(String number, long date, int duration, int type) {
+ insertValues(getValuesToInsert(number, date, duration, type));
+ }
+
+ /** Inserts the given values in the cursor. */
+ private void insertValues(Object[] values) {
+ mCursor.addRow(values);
+ ++mIndex;
+ }
+
+ /**
+ * Returns the values for a new call entry.
+ *
+ * @param number The phone number. For unknown and private numbers,
+ * use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
+ * @param date In millisec since epoch. Use NOW to use the current time.
+ * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+ * @param type Either Call.OUTGOING_TYPE or Call.INCOMING_TYPE or Call.MISSED_TYPE.
+ */
+ private Object[] getValuesToInsert(String number, long date, int duration, int type) {
+ Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
+ values[CallLogQuery.ID] = mIndex;
+ values[CallLogQuery.NUMBER] = number;
+ values[CallLogQuery.DATE] = date == NOW ? new Date().getTime() : date;
+ values[CallLogQuery.DURATION] = duration < 0 ? mRnd.nextInt(10 * 60) : duration;
+ if (mVoicemail != null && mVoicemail.equals(number)) {
+ assertEquals(Calls.OUTGOING_TYPE, type);
+ }
+ values[CallLogQuery.CALL_TYPE] = type;
+ values[CallLogQuery.COUNTRY_ISO] = TEST_COUNTRY_ISO;
+ values[CallLogQuery.SECTION] = CallLogQuery.SECTION_OLD_ITEM;
+ return values;
+ }
+
+ /**
+ * Insert a new voicemail entry in the test DB.
+ * @param number The phone number. For unknown and private numbers,
+ * use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
+ * @param date In millisec since epoch. Use NOW to use the current time.
+ * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+ */
+ private void insertVoicemail(String number, long date, int duration) {
+ Object[] values = getValuesToInsert(number, date, duration, Calls.VOICEMAIL_TYPE);
+ // Must have the same index as the row.
+ values[CallLogQuery.VOICEMAIL_URI] =
+ ContentUris.withAppendedId(VoicemailContract.Voicemails.CONTENT_URI, mIndex);
+ insertValues(values);
+ }
+
+ /**
+ * Insert a new private call entry in the test DB.
+ * @param date In millisec since epoch. Use NOW to use the current time.
+ * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+ */
+ private void insertPrivate(long date, int duration) {
+ insert(CallerInfo.PRIVATE_NUMBER, date, duration, Calls.INCOMING_TYPE);
+ }
+
+ /**
+ * Insert a new unknown call entry in the test DB.
+ * @param date In millisec since epoch. Use NOW to use the current time.
+ * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+ */
+ private void insertUnknown(long date, int duration) {
+ insert(CallerInfo.UNKNOWN_NUMBER, date, duration, Calls.INCOMING_TYPE);
+ }
+
+ /**
+ * Insert a new call to voicemail entry in the test DB.
+ * @param date In millisec since epoch. Use NOW to use the current time.
+ * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+ */
+ private void insertCalltoVoicemail(long date, int duration) {
+ // mVoicemail may be null
+ if (mVoicemail != null) {
+ insert(mVoicemail, date, duration, Calls.OUTGOING_TYPE);
+ }
+ }
+
+ /**
+ * Insert a range [start, end) of random numbers in the DB. For
+ * each row, there is a 1/10 probability that the number will be
+ * marked as PRIVATE or UNKNOWN or VOICEMAIL. For regular numbers, a number is
+ * inserted, its last 4 digits will be the number of the iteration
+ * in the range.
+ * @param start Of the range.
+ * @param end Of the range (excluded).
+ * @return An array with 2 booleans [0 = private number, 1 =
+ * unknown number, 2 = voicemail] to indicate if at least one
+ * private or unknown or voicemail number has been inserted. Since
+ * the numbers are random some tests may want to enforce the
+ * insertion of such numbers.
+ */
+ // TODO: Should insert numbers with contact entries too.
+ private boolean[] insertRandomRange(int start, int end) {
+ boolean[] privateOrUnknownOrVm = new boolean[] {false, false, false};
+
+ for (int i = start; i < end; i++ ) {
+ int type = mRnd.nextInt(10);
+
+ if (0 == type) {
+ insertPrivate(NOW, RAND_DURATION);
+ privateOrUnknownOrVm[0] = true;
+ } else if (1 == type) {
+ insertUnknown(NOW, RAND_DURATION);
+ privateOrUnknownOrVm[1] = true;
+ } else if (2 == type) {
+ insertCalltoVoicemail(NOW, RAND_DURATION);
+ privateOrUnknownOrVm[2] = true;
+ } else {
+ int inout = mRnd.nextBoolean() ? Calls.OUTGOING_TYPE : Calls.INCOMING_TYPE;
+ String number = new Formatter().format("1800123%04d", i).toString();
+ insert(number, NOW, RAND_DURATION, inout);
+ }
+ }
+ return privateOrUnknownOrVm;
+ }
+
+ /** Asserts that the name text view is shown and contains the given text. */
+ private void assertNameIs(CallLogListItemViews views, String name) {
+ assertEquals(View.VISIBLE, views.phoneCallDetailsViews.nameView.getVisibility());
+ assertEquals(name, views.phoneCallDetailsViews.nameView.getText());
+ }
+
+ /** Asserts that the number and label text view contains the given text. */
+ private void assertNumberAndLabelAre(CallLogListItemViews views, CharSequence number,
+ CharSequence label) {
+ assertEquals(View.VISIBLE, views.phoneCallDetailsViews.numberView.getVisibility());
+ assertEquals(number, views.phoneCallDetailsViews.numberView.getText().toString());
+
+ assertEquals(label == null ? View.GONE : View.VISIBLE,
+ views.phoneCallDetailsViews.labelView.getVisibility());
+ if (label != null) {
+ assertEquals(label, views.phoneCallDetailsViews.labelView.getText().toString());
+ }
+ }
+}
diff --git a/tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java b/tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java
new file mode 100644
index 000000000..6c20afe1d
--- /dev/null
+++ b/tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.calllog;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+import android.database.MatrixCursor;
+import android.provider.CallLog.Calls;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.List;
+
+/**
+ * Unit tests for {@link CallLogGroupBuilder}
+ */
+@SmallTest
+public class CallLogGroupBuilderTest extends AndroidTestCase {
+ /** A phone number for testing. */
+ private static final String TEST_NUMBER1 = "14125551234";
+ /** A phone number for testing. */
+ private static final String TEST_NUMBER2 = "14125555555";
+
+ /** The object under test. */
+ private CallLogGroupBuilder mBuilder;
+ /** Records the created groups. */
+ private FakeGroupCreator mFakeGroupCreator;
+ /** Cursor to store the values. */
+ private MatrixCursor mCursor;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mFakeGroupCreator = new FakeGroupCreator();
+ mBuilder = new CallLogGroupBuilder(mFakeGroupCreator);
+ createCursor();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mCursor = null;
+ mBuilder = null;
+ mFakeGroupCreator = null;
+ super.tearDown();
+ }
+
+ public void testAddGroups_NoCalls() {
+ mBuilder.addGroups(mCursor);
+ assertEquals(0, mFakeGroupCreator.groups.size());
+ }
+
+ public void testAddGroups_OneCall() {
+ addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
+ mBuilder.addGroups(mCursor);
+ assertEquals(0, mFakeGroupCreator.groups.size());
+ }
+
+ public void testAddGroups_TwoCallsNotMatching() {
+ addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
+ addOldCallLogEntry(TEST_NUMBER2, Calls.INCOMING_TYPE);
+ mBuilder.addGroups(mCursor);
+ assertEquals(0, mFakeGroupCreator.groups.size());
+ }
+
+ public void testAddGroups_ThreeCallsMatching() {
+ addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
+ addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
+ addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
+ mBuilder.addGroups(mCursor);
+ assertEquals(1, mFakeGroupCreator.groups.size());
+ assertGroupIs(0, 3, false, mFakeGroupCreator.groups.get(0));
+ }
+
+ public void testAddGroups_MatchingIncomingAndOutgoing() {
+ addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
+ addOldCallLogEntry(TEST_NUMBER1, Calls.OUTGOING_TYPE);
+ addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
+ mBuilder.addGroups(mCursor);
+ assertEquals(1, mFakeGroupCreator.groups.size());
+ assertGroupIs(0, 3, false, mFakeGroupCreator.groups.get(0));
+ }
+
+ public void testAddGroups_HeaderSplitsGroups() {
+ addNewCallLogHeader();
+ addNewCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
+ addNewCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
+ addOldCallLogHeader();
+ addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
+ addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
+ mBuilder.addGroups(mCursor);
+ assertEquals(2, mFakeGroupCreator.groups.size());
+ assertGroupIs(1, 2, false, mFakeGroupCreator.groups.get(0));
+ assertGroupIs(4, 2, false, mFakeGroupCreator.groups.get(1));
+ }
+
+ public void testAddGroups_Voicemail() {
+ // Does not group with other types of calls, include voicemail themselves.
+ assertCallsAreNotGrouped(Calls.VOICEMAIL_TYPE, Calls.MISSED_TYPE);
+ //assertCallsAreNotGrouped(Calls.VOICEMAIL_TYPE, Calls.MISSED_TYPE, Calls.MISSED_TYPE);
+ assertCallsAreNotGrouped(Calls.VOICEMAIL_TYPE, Calls.VOICEMAIL_TYPE);
+ assertCallsAreNotGrouped(Calls.VOICEMAIL_TYPE, Calls.INCOMING_TYPE);
+ assertCallsAreNotGrouped(Calls.VOICEMAIL_TYPE, Calls.OUTGOING_TYPE);
+ }
+
+ public void testAddGroups_Missed() {
+ // Groups with one or more missed calls.
+ assertCallsAreGrouped(Calls.MISSED_TYPE, Calls.MISSED_TYPE);
+ assertCallsAreGrouped(Calls.MISSED_TYPE, Calls.MISSED_TYPE, Calls.MISSED_TYPE);
+ // Does not group with other types of calls.
+ assertCallsAreNotGrouped(Calls.MISSED_TYPE, Calls.VOICEMAIL_TYPE);
+ assertCallsAreGrouped(Calls.MISSED_TYPE, Calls.INCOMING_TYPE);
+ assertCallsAreGrouped(Calls.MISSED_TYPE, Calls.OUTGOING_TYPE);
+ }
+
+ public void testAddGroups_Incoming() {
+ // Groups with one or more incoming or outgoing.
+ assertCallsAreGrouped(Calls.INCOMING_TYPE, Calls.INCOMING_TYPE);
+ assertCallsAreGrouped(Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
+ assertCallsAreGrouped(Calls.INCOMING_TYPE, Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
+ assertCallsAreGrouped(Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE, Calls.INCOMING_TYPE);
+ assertCallsAreGrouped(Calls.INCOMING_TYPE, Calls.MISSED_TYPE);
+ // Does not group with voicemail and missed calls.
+ assertCallsAreNotGrouped(Calls.INCOMING_TYPE, Calls.VOICEMAIL_TYPE);
+ }
+
+ public void testAddGroups_Outgoing() {
+ // Groups with one or more incoming or outgoing.
+ assertCallsAreGrouped(Calls.OUTGOING_TYPE, Calls.INCOMING_TYPE);
+ assertCallsAreGrouped(Calls.OUTGOING_TYPE, Calls.OUTGOING_TYPE);
+ assertCallsAreGrouped(Calls.OUTGOING_TYPE, Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
+ assertCallsAreGrouped(Calls.OUTGOING_TYPE, Calls.OUTGOING_TYPE, Calls.INCOMING_TYPE);
+ assertCallsAreGrouped(Calls.INCOMING_TYPE, Calls.MISSED_TYPE);
+ // Does not group with voicemail and missed calls.
+ assertCallsAreNotGrouped(Calls.INCOMING_TYPE, Calls.VOICEMAIL_TYPE);
+ }
+
+ public void testAddGroups_Mixed() {
+ addMultipleOldCallLogEntries(TEST_NUMBER1,
+ Calls.VOICEMAIL_TYPE, // Stand-alone
+ Calls.INCOMING_TYPE, // Group 1: 1-4
+ Calls.OUTGOING_TYPE,
+ Calls.MISSED_TYPE,
+ Calls.MISSED_TYPE,
+ Calls.VOICEMAIL_TYPE, // Stand-alone
+ Calls.INCOMING_TYPE, // Stand-alone
+ Calls.VOICEMAIL_TYPE, // Stand-alone
+ Calls.MISSED_TYPE, // Group 2: 8-10
+ Calls.MISSED_TYPE,
+ Calls.OUTGOING_TYPE);
+ mBuilder.addGroups(mCursor);
+ assertEquals(2, mFakeGroupCreator.groups.size());
+ assertGroupIs(1, 4, false, mFakeGroupCreator.groups.get(0));
+ assertGroupIs(8, 3, false, mFakeGroupCreator.groups.get(1));
+ }
+
+ public void testEqualPhoneNumbers() {
+ // Identical.
+ assertTrue(mBuilder.equalNumbers("6505555555", "6505555555"));
+ assertTrue(mBuilder.equalNumbers("650 555 5555", "650 555 5555"));
+ // Formatting.
+ assertTrue(mBuilder.equalNumbers("6505555555", "650 555 5555"));
+ assertTrue(mBuilder.equalNumbers("6505555555", "(650) 555-5555"));
+ assertTrue(mBuilder.equalNumbers("650 555 5555", "(650) 555-5555"));
+ // Short codes.
+ assertTrue(mBuilder.equalNumbers("55555", "55555"));
+ assertTrue(mBuilder.equalNumbers("55555", "555 55"));
+ // Different numbers.
+ assertFalse(mBuilder.equalNumbers("6505555555", "650555555"));
+ assertFalse(mBuilder.equalNumbers("6505555555", "6505555551"));
+ assertFalse(mBuilder.equalNumbers("650 555 5555", "650 555 555"));
+ assertFalse(mBuilder.equalNumbers("650 555 5555", "650 555 5551"));
+ assertFalse(mBuilder.equalNumbers("55555", "5555"));
+ assertFalse(mBuilder.equalNumbers("55555", "55551"));
+ // SIP addresses.
+ assertTrue(mBuilder.equalNumbers("6505555555@host.com", "6505555555@host.com"));
+ assertTrue(mBuilder.equalNumbers("6505555555@host.com", "6505555555@HOST.COM"));
+ assertTrue(mBuilder.equalNumbers("user@host.com", "user@host.com"));
+ assertTrue(mBuilder.equalNumbers("user@host.com", "user@HOST.COM"));
+ assertFalse(mBuilder.equalNumbers("USER@host.com", "user@host.com"));
+ assertFalse(mBuilder.equalNumbers("user@host.com", "user@host1.com"));
+ // SIP address vs phone number.
+ assertFalse(mBuilder.equalNumbers("6505555555@host.com", "6505555555"));
+ assertFalse(mBuilder.equalNumbers("6505555555", "6505555555@host.com"));
+ assertFalse(mBuilder.equalNumbers("user@host.com", "6505555555"));
+ assertFalse(mBuilder.equalNumbers("6505555555", "user@host.com"));
+ // Nulls.
+ assertTrue(mBuilder.equalNumbers(null, null));
+ assertFalse(mBuilder.equalNumbers(null, "6505555555"));
+ assertFalse(mBuilder.equalNumbers("6505555555", null));
+ assertFalse(mBuilder.equalNumbers(null, "6505555555@host.com"));
+ assertFalse(mBuilder.equalNumbers("6505555555@host.com", null));
+ }
+
+ public void testCompareSipAddresses() {
+ // Identical.
+ assertTrue(mBuilder.compareSipAddresses("6505555555@host.com", "6505555555@host.com"));
+ assertTrue(mBuilder.compareSipAddresses("user@host.com", "user@host.com"));
+ // Host is case insensitive.
+ assertTrue(mBuilder.compareSipAddresses("6505555555@host.com", "6505555555@HOST.COM"));
+ assertTrue(mBuilder.compareSipAddresses("user@host.com", "user@HOST.COM"));
+ // Userinfo is case sensitive.
+ assertFalse(mBuilder.compareSipAddresses("USER@host.com", "user@host.com"));
+ // Different hosts.
+ assertFalse(mBuilder.compareSipAddresses("user@host.com", "user@host1.com"));
+ // Different users.
+ assertFalse(mBuilder.compareSipAddresses("user1@host.com", "user@host.com"));
+ // Nulls.
+ assertTrue(mBuilder.compareSipAddresses(null, null));
+ assertFalse(mBuilder.compareSipAddresses(null, "6505555555@host.com"));
+ assertFalse(mBuilder.compareSipAddresses("6505555555@host.com", null));
+ }
+
+ /** Creates (or recreates) the cursor used to store the call log content for the tests. */
+ private void createCursor() {
+ mCursor = new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
+ }
+
+ /** Clears the content of the {@link FakeGroupCreator} used in the tests. */
+ private void clearFakeGroupCreator() {
+ mFakeGroupCreator.groups.clear();
+ }
+
+ /** Asserts that calls of the given types are grouped together into a single group. */
+ private void assertCallsAreGrouped(int... types) {
+ createCursor();
+ clearFakeGroupCreator();
+ addMultipleOldCallLogEntries(TEST_NUMBER1, types);
+ mBuilder.addGroups(mCursor);
+ assertEquals(1, mFakeGroupCreator.groups.size());
+ assertGroupIs(0, types.length, false, mFakeGroupCreator.groups.get(0));
+
+ }
+
+ /** Asserts that calls of the given types are not grouped together at all. */
+ private void assertCallsAreNotGrouped(int... types) {
+ createCursor();
+ clearFakeGroupCreator();
+ addMultipleOldCallLogEntries(TEST_NUMBER1, types);
+ mBuilder.addGroups(mCursor);
+ assertEquals(0, mFakeGroupCreator.groups.size());
+ }
+
+ /** Adds a set of calls with the given types, all from the same number, in the old section. */
+ private void addMultipleOldCallLogEntries(String number, int... types) {
+ for (int type : types) {
+ addOldCallLogEntry(number, type);
+ }
+ }
+
+ /** Adds a call with the given number and type to the old section of the call log. */
+ private void addOldCallLogEntry(String number, int type) {
+ addCallLogEntry(number, type, CallLogQuery.SECTION_OLD_ITEM);
+ }
+
+ /** Adds a call with the given number and type to the new section of the call log. */
+ private void addNewCallLogEntry(String number, int type) {
+ addCallLogEntry(number, type, CallLogQuery.SECTION_NEW_ITEM);
+ }
+
+ /** Adds a call log entry with the given number and type to the cursor. */
+ private void addCallLogEntry(String number, int type, int section) {
+ if (section != CallLogQuery.SECTION_NEW_ITEM
+ && section != CallLogQuery.SECTION_OLD_ITEM) {
+ throw new IllegalArgumentException("not an item section: " + section);
+ }
+ mCursor.moveToNext();
+ Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
+ values[CallLogQuery.ID] = mCursor.getPosition();
+ values[CallLogQuery.NUMBER] = number;
+ values[CallLogQuery.CALL_TYPE] = type;
+ values[CallLogQuery.SECTION] = section;
+ mCursor.addRow(values);
+ }
+
+ /** Adds the old section header to the call log. */
+ private void addOldCallLogHeader() {
+ addCallLogHeader(CallLogQuery.SECTION_OLD_HEADER);
+ }
+
+ /** Adds the new section header to the call log. */
+ private void addNewCallLogHeader() {
+ addCallLogHeader(CallLogQuery.SECTION_NEW_HEADER);
+ }
+
+ /** Adds a call log entry with a header to the cursor. */
+ private void addCallLogHeader(int section) {
+ if (section != CallLogQuery.SECTION_NEW_HEADER
+ && section != CallLogQuery.SECTION_OLD_HEADER) {
+ throw new IllegalArgumentException("not a header section: " + section);
+ }
+ mCursor.moveToNext();
+ Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
+ values[CallLogQuery.ID] = mCursor.getPosition();
+ values[CallLogQuery.SECTION] = section;
+ mCursor.addRow(values);
+ }
+
+ /** Asserts that the group matches the given values. */
+ private void assertGroupIs(int cursorPosition, int size, boolean expanded, GroupSpec group) {
+ assertEquals(cursorPosition, group.cursorPosition);
+ assertEquals(size, group.size);
+ assertEquals(expanded, group.expanded);
+ }
+
+ /** Defines an added group. Used by the {@link FakeGroupCreator}. */
+ private static class GroupSpec {
+ /** The starting position of the group. */
+ public final int cursorPosition;
+ /** The number of elements in the group. */
+ public final int size;
+ /** Whether the group should be initially expanded. */
+ public final boolean expanded;
+
+ public GroupSpec(int cursorPosition, int size, boolean expanded) {
+ this.cursorPosition = cursorPosition;
+ this.size = size;
+ this.expanded = expanded;
+ }
+ }
+
+ /** Fake implementation of a GroupCreator which stores the created groups in a member field. */
+ private static class FakeGroupCreator implements CallLogGroupBuilder.GroupCreator {
+ /** The list of created groups. */
+ public final List<GroupSpec> groups = newArrayList();
+
+ @Override
+ public void addGroup(int cursorPosition, int size, boolean expanded) {
+ groups.add(new GroupSpec(cursorPosition, size, expanded));
+ }
+ }
+}
diff --git a/tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java b/tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java
new file mode 100644
index 000000000..3ad5abe0a
--- /dev/null
+++ b/tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.calllog;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.CallLog.Calls;
+import android.test.AndroidTestCase;
+import android.view.View;
+
+import com.android.dialer.PhoneCallDetails;
+import com.android.dialer.PhoneCallDetailsHelper;
+import com.android.internal.telephony.CallerInfo;
+
+/**
+ * Unit tests for {@link CallLogListItemHelper}.
+ */
+public class CallLogListItemHelperTest extends AndroidTestCase {
+ /** A test phone number for phone calls. */
+ private static final String TEST_NUMBER = "14125555555";
+ /** The formatted version of {@link #TEST_NUMBER}. */
+ private static final String TEST_FORMATTED_NUMBER = "1-412-255-5555";
+ /** A test date value for phone calls. */
+ private static final long TEST_DATE = 1300000000;
+ /** A test duration value for phone calls. */
+ private static final long TEST_DURATION = 62300;
+ /** A test voicemail number. */
+ private static final String TEST_VOICEMAIL_NUMBER = "123";
+ /** The country ISO name used in the tests. */
+ private static final String TEST_COUNTRY_ISO = "US";
+ /** The geocoded location used in the tests. */
+ private static final String TEST_GEOCODE = "United States";
+
+ /** The object under test. */
+ private CallLogListItemHelper mHelper;
+
+ /** The views used in the tests. */
+ private CallLogListItemViews mViews;
+ private PhoneNumberHelper mPhoneNumberHelper;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ Context context = getContext();
+ Resources resources = context.getResources();
+ CallTypeHelper callTypeHelper = new CallTypeHelper(resources);
+ mPhoneNumberHelper = new TestPhoneNumberHelper(resources, TEST_VOICEMAIL_NUMBER);
+ PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper(
+ resources, callTypeHelper, mPhoneNumberHelper);
+ mHelper = new CallLogListItemHelper(phoneCallDetailsHelper, mPhoneNumberHelper, resources);
+ mViews = CallLogListItemViews.createForTest(context);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mHelper = null;
+ mViews = null;
+ super.tearDown();
+ }
+
+ public void testSetPhoneCallDetails() {
+ setPhoneCallDetailsWithNumber("12125551234", "1-212-555-1234");
+ assertEquals(View.VISIBLE, mViews.secondaryActionView.getVisibility());
+ }
+
+ public void testSetPhoneCallDetails_Unknown() {
+ setPhoneCallDetailsWithNumber(CallerInfo.UNKNOWN_NUMBER, CallerInfo.UNKNOWN_NUMBER);
+ assertNoCallButton();
+ }
+
+ public void testSetPhoneCallDetails_Private() {
+ setPhoneCallDetailsWithNumber(CallerInfo.PRIVATE_NUMBER, CallerInfo.PRIVATE_NUMBER);
+ assertNoCallButton();
+ }
+
+ public void testSetPhoneCallDetails_Payphone() {
+ setPhoneCallDetailsWithNumber(CallerInfo.PAYPHONE_NUMBER, CallerInfo.PAYPHONE_NUMBER);
+ assertNoCallButton();
+ }
+
+ public void testSetPhoneCallDetails_VoicemailNumber() {
+ setPhoneCallDetailsWithNumber(TEST_VOICEMAIL_NUMBER, TEST_VOICEMAIL_NUMBER);
+ assertEquals(View.VISIBLE, mViews.secondaryActionView.getVisibility());
+ }
+
+ public void testSetPhoneCallDetails_ReadVoicemail() {
+ setPhoneCallDetailsWithTypes(Calls.VOICEMAIL_TYPE);
+ assertEquals(View.VISIBLE, mViews.secondaryActionView.getVisibility());
+ }
+
+ public void testSetPhoneCallDetails_UnreadVoicemail() {
+ setUnreadPhoneCallDetailsWithTypes(Calls.VOICEMAIL_TYPE);
+ assertEquals(View.VISIBLE, mViews.secondaryActionView.getVisibility());
+ }
+
+ public void testSetPhoneCallDetails_VoicemailFromUnknown() {
+ setPhoneCallDetailsWithNumberAndType(CallerInfo.UNKNOWN_NUMBER, CallerInfo.UNKNOWN_NUMBER,
+ Calls.VOICEMAIL_TYPE);
+ assertEquals(View.VISIBLE, mViews.secondaryActionView.getVisibility());
+ }
+
+ /** Asserts that the whole call area is gone. */
+ private void assertNoCallButton() {
+ assertEquals(View.GONE, mViews.secondaryActionView.getVisibility());
+ assertEquals(View.GONE, mViews.dividerView.getVisibility());
+ }
+
+ /** Sets the details of a phone call using the specified phone number. */
+ private void setPhoneCallDetailsWithNumber(String number, String formattedNumber) {
+ setPhoneCallDetailsWithNumberAndType(number, formattedNumber, Calls.INCOMING_TYPE);
+ }
+
+ /** Sets the details of a phone call using the specified phone number. */
+ private void setPhoneCallDetailsWithNumberAndType(String number, String formattedNumber,
+ int callType) {
+ mHelper.setPhoneCallDetails(mViews,
+ new PhoneCallDetails(number, formattedNumber, TEST_COUNTRY_ISO, TEST_GEOCODE,
+ new int[]{ callType }, TEST_DATE, TEST_DURATION),
+ false);
+ }
+
+ /** Sets the details of a phone call using the specified call type. */
+ private void setPhoneCallDetailsWithTypes(int... types) {
+ mHelper.setPhoneCallDetails(mViews,
+ new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
+ TEST_GEOCODE, types, TEST_DATE, TEST_DURATION),
+ false);
+ }
+
+ /** Sets the details of a phone call using the specified call type. */
+ private void setUnreadPhoneCallDetailsWithTypes(int... types) {
+ mHelper.setPhoneCallDetails(mViews,
+ new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
+ TEST_GEOCODE, types, TEST_DATE, TEST_DURATION),
+ true);
+ }
+}
diff --git a/tests/src/com/android/dialer/calllog/CallLogQueryTestUtils.java b/tests/src/com/android/dialer/calllog/CallLogQueryTestUtils.java
new file mode 100644
index 000000000..4be84aede
--- /dev/null
+++ b/tests/src/com/android/dialer/calllog/CallLogQueryTestUtils.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.calllog;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.provider.CallLog.Calls;
+
+import junit.framework.Assert;
+
+/**
+ * Helper class to create test values for {@link CallLogQuery}.
+ */
+public class CallLogQueryTestUtils {
+ public static Object[] createTestValues() {
+ Object[] values = new Object[]{
+ 0L, "", 0L, 0L, Calls.INCOMING_TYPE, "", "", "", null, 0, null, null, null, null,
+ 0L, null, 0,
+ };
+ assertEquals(CallLogQuery._PROJECTION.length, values.length);
+ return values;
+ }
+
+ public static Object[] createTestExtendedValues() {
+ Object[] values = new Object[]{
+ 0L, "", 0L, 0L, Calls.INCOMING_TYPE, "", "", "", null, 0, null, null, null, null,
+ 0L, null, 1, CallLogQuery.SECTION_OLD_ITEM
+ };
+ Assert.assertEquals(CallLogQuery.EXTENDED_PROJECTION.length, values.length);
+ return values;
+ }
+}
diff --git a/tests/src/com/android/dialer/calllog/TestPhoneNumberHelper.java b/tests/src/com/android/dialer/calllog/TestPhoneNumberHelper.java
new file mode 100644
index 000000000..144635945
--- /dev/null
+++ b/tests/src/com/android/dialer/calllog/TestPhoneNumberHelper.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.calllog;
+
+import android.content.res.Resources;
+
+/**
+ * Modified version of {@link PhoneNumberHelper} to be used in tests that allows injecting the
+ * voicemail number.
+ */
+public final class TestPhoneNumberHelper extends PhoneNumberHelper {
+ private CharSequence mVoicemailNumber;
+
+ public TestPhoneNumberHelper(Resources resources, CharSequence voicemailNumber) {
+ super(resources);
+ mVoicemailNumber = voicemailNumber;
+ }
+
+ @Override
+ public boolean isVoicemailNumber(CharSequence number) {
+ return mVoicemailNumber.equals(number);
+ }
+}
diff --git a/tests/src/com/android/dialer/tests/calllog/FillCallLogTestActivity.java b/tests/src/com/android/dialer/tests/calllog/FillCallLogTestActivity.java
new file mode 100644
index 000000000..ed492205c
--- /dev/null
+++ b/tests/src/com/android/dialer/tests/calllog/FillCallLogTestActivity.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.tests.calllog;
+
+import android.app.Activity;
+import android.app.LoaderManager;
+import android.content.ContentProviderClient;
+import android.content.ContentValues;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.CallLog.Calls;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.dialer.tests.R;
+
+import java.util.Random;
+
+/**
+ * Activity to add entries to the call log for testing.
+ */
+public class FillCallLogTestActivity extends Activity {
+ private static final String TAG = "FillCallLogTestActivity";
+ /** Identifier of the loader for querying the call log. */
+ private static final int CALLLOG_LOADER_ID = 1;
+
+ private static final Random RNG = new Random();
+ private static final int[] CALL_TYPES = new int[] {
+ Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE, Calls.MISSED_TYPE,
+ };
+
+ private TextView mNumberTextView;
+ private Button mAddButton;
+ private ProgressBar mProgressBar;
+ private CheckBox mUseRandomNumbers;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.fill_call_log_test);
+ mNumberTextView = (TextView) findViewById(R.id.number);
+ mAddButton = (Button) findViewById(R.id.add);
+ mProgressBar = (ProgressBar) findViewById(R.id.progress);
+ mUseRandomNumbers = (CheckBox) findViewById(R.id.use_random_numbers);
+
+ mAddButton.setOnClickListener(new View.OnClickListener(){
+ @Override
+ public void onClick(View v) {
+ int count;
+ try {
+ count = Integer.parseInt(mNumberTextView.getText().toString());
+ if (count > 100) {
+ throw new RuntimeException("Number too large. Max=100");
+ }
+ } catch (RuntimeException e) {
+ Toast.makeText(FillCallLogTestActivity.this, e.toString(), Toast.LENGTH_LONG)
+ .show();
+ return;
+ }
+ addEntriesToCallLog(count, mUseRandomNumbers.isChecked());
+ mNumberTextView.setEnabled(false);
+ mAddButton.setEnabled(false);
+ mProgressBar.setProgress(0);
+ mProgressBar.setMax(count);
+ mProgressBar.setVisibility(View.VISIBLE);
+ }
+ });
+ }
+
+ /**
+ * Adds a number of entries to the call log. The content of the entries is based on existing
+ * entries.
+ *
+ * @param count the number of entries to add
+ */
+ private void addEntriesToCallLog(final int count, boolean useRandomNumbers) {
+ if (useRandomNumbers) {
+ addRandomNumbers(count);
+ } else {
+ getLoaderManager().initLoader(CALLLOG_LOADER_ID, null,
+ new CallLogLoaderListener(count));
+ }
+ }
+
+ /**
+ * Calls when the insertion has completed.
+ *
+ * @param message the message to show in a toast to the user
+ */
+ private void insertCompleted(String message) {
+ // Hide the progress bar.
+ mProgressBar.setVisibility(View.GONE);
+ // Re-enable the add button.
+ mNumberTextView.setEnabled(true);
+ mAddButton.setEnabled(true);
+ mNumberTextView.setText("");
+ Toast.makeText(this, message, Toast.LENGTH_LONG).show();
+ }
+
+
+ /**
+ * Creates a {@link ContentValues} object containing values corresponding to the given cursor.
+ *
+ * @param cursor the cursor from which to get the values
+ * @return a newly created content values object
+ */
+ private ContentValues createContentValuesFromCursor(Cursor cursor) {
+ ContentValues values = new ContentValues();
+ for (int column = 0; column < cursor.getColumnCount();
+ ++column) {
+ String name = cursor.getColumnName(column);
+ switch (cursor.getType(column)) {
+ case Cursor.FIELD_TYPE_STRING:
+ values.put(name, cursor.getString(column));
+ break;
+ case Cursor.FIELD_TYPE_INTEGER:
+ values.put(name, cursor.getLong(column));
+ break;
+ case Cursor.FIELD_TYPE_FLOAT:
+ values.put(name, cursor.getDouble(column));
+ break;
+ case Cursor.FIELD_TYPE_BLOB:
+ values.put(name, cursor.getBlob(column));
+ break;
+ case Cursor.FIELD_TYPE_NULL:
+ values.putNull(name);
+ break;
+ default:
+ Log.d(TAG, "Invalid value in cursor: " + cursor.getType(column));
+ break;
+ }
+ }
+ return values;
+ }
+
+ private void addRandomNumbers(int count) {
+ ContentValues[] values = new ContentValues[count];
+ for (int i = 0; i < count; i++) {
+ values[i] = new ContentValues();
+ values[i].put(Calls.NUMBER, generateRandomNumber());
+ values[i].put(Calls.DATE, System.currentTimeMillis()); // Will be randomized later
+ values[i].put(Calls.DURATION, 1); // Will be overwritten later
+ }
+ new AsyncCallLogInserter(values).execute(new Void[0]);
+ }
+
+ private static String generateRandomNumber() {
+ return String.format("5%09d", RNG.nextInt(1000000000));
+ }
+
+ /** Invokes {@link AsyncCallLogInserter} when the call log has loaded. */
+ private final class CallLogLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> {
+ /** The number of items to insert when done. */
+ private final int mCount;
+
+ private CallLogLoaderListener(int count) {
+ mCount = count;
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ Log.d(TAG, "onCreateLoader");
+ return new CursorLoader(FillCallLogTestActivity.this, Calls.CONTENT_URI,
+ null, null, null, null);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ try {
+ Log.d(TAG, "onLoadFinished");
+
+ if (data.getCount() == 0) {
+ // If there are no entries in the call log, we cannot generate new ones.
+ insertCompleted(getString(R.string.noLogEntriesToast));
+ return;
+ }
+
+ data.moveToPosition(-1);
+
+ ContentValues[] values = new ContentValues[mCount];
+ for (int index = 0; index < mCount; ++index) {
+ if (!data.moveToNext()) {
+ data.moveToFirst();
+ }
+ values[index] = createContentValuesFromCursor(data);
+ }
+ new AsyncCallLogInserter(values).execute(new Void[0]);
+ } finally {
+ // This is a one shot loader.
+ getLoaderManager().destroyLoader(CALLLOG_LOADER_ID);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {}
+ }
+
+ /** Inserts a given number of entries in the call log based on the values given. */
+ private final class AsyncCallLogInserter extends AsyncTask<Void, Integer, Integer> {
+ /** The number of items to insert. */
+ private final ContentValues[] mValues;
+
+ public AsyncCallLogInserter(ContentValues[] values) {
+ mValues = values;
+ }
+
+ @Override
+ protected Integer doInBackground(Void... params) {
+ Log.d(TAG, "doInBackground");
+ return insertIntoCallLog();
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... values) {
+ Log.d(TAG, "onProgressUpdate");
+ updateCount(values[0]);
+ }
+
+ @Override
+ protected void onPostExecute(Integer count) {
+ Log.d(TAG, "onPostExecute");
+ insertCompleted(getString(R.string.addedLogEntriesToast, count));
+ }
+
+ /**
+ * Inserts a number of entries in the call log based on the given templates.
+ *
+ * @return the number of inserted entries
+ */
+ private Integer insertIntoCallLog() {
+ int inserted = 0;
+
+ for (int index = 0; index < mValues.length; ++index) {
+ ContentValues values = mValues[index];
+ // These should not be set.
+ values.putNull(Calls._ID);
+ // Add some randomness to the date. For each new entry being added, add an extra
+ // day to the maximum possible offset from the original.
+ values.put(Calls.DATE,
+ values.getAsLong(Calls.DATE)
+ - RNG.nextInt(24 * 60 * 60 * (index + 1)) * 1000L);
+ // Add some randomness to the duration.
+ if (values.getAsLong(Calls.DURATION) > 0) {
+ values.put(Calls.DURATION, RNG.nextInt(30 * 60 * 60 * 1000));
+ }
+
+ // Overwrite type.
+ values.put(Calls.TYPE, CALL_TYPES[RNG.nextInt(CALL_TYPES.length)]);
+
+ // Clear cached columns.
+ values.putNull(Calls.CACHED_FORMATTED_NUMBER);
+ values.putNull(Calls.CACHED_LOOKUP_URI);
+ values.putNull(Calls.CACHED_MATCHED_NUMBER);
+ values.putNull(Calls.CACHED_NAME);
+ values.putNull(Calls.CACHED_NORMALIZED_NUMBER);
+ values.putNull(Calls.CACHED_NUMBER_LABEL);
+ values.putNull(Calls.CACHED_NUMBER_TYPE);
+ values.putNull(Calls.CACHED_PHOTO_ID);
+
+ // Insert into the call log the newly generated entry.
+ ContentProviderClient contentProvider =
+ getContentResolver().acquireContentProviderClient(
+ Calls.CONTENT_URI);
+ try {
+ Log.d(TAG, "adding entry to call log");
+ contentProvider.insert(Calls.CONTENT_URI, values);
+ ++inserted;
+ this.publishProgress(inserted);
+ } catch (RemoteException e) {
+ Log.d(TAG, "insert failed", e);
+ }
+ }
+ return inserted;
+ }
+ }
+
+ /**
+ * Updates the count shown to the user corresponding to the number of entries added.
+ *
+ * @param count the number of entries inserted so far
+ */
+ public void updateCount(Integer count) {
+ mProgressBar.setProgress(count);
+ }
+}
diff --git a/tests/src/com/android/dialer/util/ExpirableCacheTest.java b/tests/src/com/android/dialer/util/ExpirableCacheTest.java
new file mode 100644
index 000000000..b81ad754f
--- /dev/null
+++ b/tests/src/com/android/dialer/util/ExpirableCacheTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.util;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.LruCache;
+
+import com.android.dialer.util.ExpirableCache.CachedValue;
+
+/**
+ * Unit tests for {@link ExpirableCache}.
+ */
+@SmallTest
+public class ExpirableCacheTest extends AndroidTestCase {
+ /** The object under test. */
+ private ExpirableCache<String, Integer> mCache;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ LruCache<String, CachedValue<Integer>> lruCache =
+ new LruCache<String, ExpirableCache.CachedValue<Integer>>(20);
+ mCache = ExpirableCache.create(lruCache);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mCache = null;
+ super.tearDown();
+ }
+
+ public void testPut() {
+ mCache.put("a", 1);
+ mCache.put("b", 2);
+ assertEquals(1, mCache.getPossiblyExpired("a").intValue());
+ assertEquals(2, mCache.getPossiblyExpired("b").intValue());
+ mCache.put("a", 3);
+ assertEquals(3, mCache.getPossiblyExpired("a").intValue());
+ }
+
+ public void testGet_NotExisting() {
+ assertNull(mCache.getPossiblyExpired("a"));
+ mCache.put("b", 1);
+ assertNull(mCache.getPossiblyExpired("a"));
+ }
+
+ public void testGet_Expired() {
+ mCache.put("a", 1);
+ assertEquals(1, mCache.getPossiblyExpired("a").intValue());
+ mCache.expireAll();
+ assertEquals(1, mCache.getPossiblyExpired("a").intValue());
+ }
+
+ public void testGetNotExpired_NotExisting() {
+ assertNull(mCache.get("a"));
+ mCache.put("b", 1);
+ assertNull(mCache.get("a"));
+ }
+
+ public void testGetNotExpired_Expired() {
+ mCache.put("a", 1);
+ assertEquals(1, mCache.get("a").intValue());
+ mCache.expireAll();
+ assertNull(mCache.get("a"));
+ }
+
+ public void testGetCachedValue_NotExisting() {
+ assertNull(mCache.getCachedValue("a"));
+ mCache.put("b", 1);
+ assertNull(mCache.getCachedValue("a"));
+ }
+
+ public void testGetCachedValue_Expired() {
+ mCache.put("a", 1);
+ assertFalse("Should not be expired", mCache.getCachedValue("a").isExpired());
+ mCache.expireAll();
+ assertTrue("Should be expired", mCache.getCachedValue("a").isExpired());
+ }
+
+ public void testGetChangedValue_PutAfterExpired() {
+ mCache.put("a", 1);
+ mCache.expireAll();
+ mCache.put("a", 1);
+ assertFalse("Should not be expired", mCache.getCachedValue("a").isExpired());
+ }
+
+ public void testComputingCache() {
+ // Creates a cache in which all unknown values default to zero.
+ mCache = ExpirableCache.create(
+ new LruCache<String, ExpirableCache.CachedValue<Integer>>(10) {
+ @Override
+ protected CachedValue<Integer> create(String key) {
+ return mCache.newCachedValue(0);
+ }
+ });
+
+ // The first time we request a new value, we add it to the cache.
+ CachedValue<Integer> cachedValue = mCache.getCachedValue("a");
+ assertNotNull("Should have been created implicitly", cachedValue);
+ assertEquals(0, cachedValue.getValue().intValue());
+ assertFalse("Should not be expired", cachedValue.isExpired());
+
+ // If we expire all the values, the implicitly created value will also be marked as expired.
+ mCache.expireAll();
+ CachedValue<Integer> expiredCachedValue = mCache.getCachedValue("a");
+ assertNotNull("Should have been created implicitly", expiredCachedValue);
+ assertEquals(0, expiredCachedValue.getValue().intValue());
+ assertTrue("Should be expired", expiredCachedValue.isExpired());
+ }
+}
diff --git a/tests/src/com/android/dialer/util/FakeAsyncTaskExecutor.java b/tests/src/com/android/dialer/util/FakeAsyncTaskExecutor.java
new file mode 100644
index 000000000..064587e4b
--- /dev/null
+++ b/tests/src/com/android/dialer/util/FakeAsyncTaskExecutor.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.util;
+
+import android.app.Instrumentation;
+import android.os.AsyncTask;
+
+import com.android.contacts.util.AsyncTaskExecutor;
+import com.android.contacts.util.AsyncTaskExecutors;
+import com.google.common.collect.Lists;
+
+import junit.framework.Assert;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Test implementation of AsyncTaskExecutor.
+ * <p>
+ * This class is thread-safe. As per the contract of the AsyncTaskExecutor, the submit methods must
+ * be called from the main ui thread, however the other public methods may be called from any thread
+ * (most commonly the test thread).
+ * <p>
+ * Tasks submitted to this executor will not be run immediately. Rather they will be stored in a
+ * list of submitted tasks, where they can be examined. They can also be run on-demand using the run
+ * methods, so that different ordering of AsyncTask execution can be simulated.
+ * <p>
+ * The onPreExecute method of the submitted AsyncTask will be called synchronously during the
+ * call to {@link #submit(Object, AsyncTask, Object...)}.
+ */
+@ThreadSafe
+public class FakeAsyncTaskExecutor implements AsyncTaskExecutor {
+ private static final long DEFAULT_TIMEOUT_MS = 10000;
+
+ /** The maximum length of time in ms to wait for tasks to execute during tests. */
+ private final long mTimeoutMs = DEFAULT_TIMEOUT_MS;
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock") private final List<SubmittedTask> mSubmittedTasks = Lists.newArrayList();
+
+ private final DelayedExecutor mBlockingExecutor = new DelayedExecutor();
+ private final Instrumentation mInstrumentation;
+
+ /** Create a fake AsyncTaskExecutor for use in unit tests. */
+ public FakeAsyncTaskExecutor(Instrumentation instrumentation) {
+ Assert.assertNotNull(instrumentation);
+ mInstrumentation = instrumentation;
+ }
+
+ /** Encapsulates an async task with the params and identifier it was submitted with. */
+ public interface SubmittedTask {
+ Runnable getRunnable();
+ Object getIdentifier();
+ AsyncTask<?, ?, ?> getAsyncTask();
+ }
+
+ private static final class SubmittedTaskImpl implements SubmittedTask {
+ private final Object mIdentifier;
+ private final Runnable mRunnable;
+ private final AsyncTask<?, ?, ?> mAsyncTask;
+
+ public SubmittedTaskImpl(Object identifier, Runnable runnable,
+ AsyncTask<?, ?, ?> asyncTask) {
+ mIdentifier = identifier;
+ mRunnable = runnable;
+ mAsyncTask = asyncTask;
+ }
+
+ @Override
+ public Object getIdentifier() {
+ return mIdentifier;
+ }
+
+ @Override
+ public Runnable getRunnable() {
+ return mRunnable;
+ }
+
+ @Override
+ public AsyncTask<?, ?, ?> getAsyncTask() {
+ return mAsyncTask;
+ }
+
+ @Override
+ public String toString() {
+ return "SubmittedTaskImpl [mIdentifier=" + mIdentifier + "]";
+ }
+ }
+
+ private class DelayedExecutor implements Executor {
+ private final Object mNextLock = new Object();
+ @GuardedBy("mNextLock") private Object mNextIdentifier;
+ @GuardedBy("mNextLock") private AsyncTask<?, ?, ?> mNextTask;
+
+ @Override
+ public void execute(Runnable command) {
+ synchronized (mNextLock) {
+ Assert.assertNotNull(mNextTask);
+ mSubmittedTasks.add(new SubmittedTaskImpl(mNextIdentifier,
+ command, mNextTask));
+ mNextIdentifier = null;
+ mNextTask = null;
+ }
+ }
+
+ public <T> AsyncTask<T, ?, ?> submit(Object identifier,
+ AsyncTask<T, ?, ?> task, T... params) {
+ synchronized (mNextLock) {
+ Assert.assertNull(mNextIdentifier);
+ Assert.assertNull(mNextTask);
+ mNextIdentifier = identifier;
+ Assert.assertNotNull("Already had a valid task.\n"
+ + "Are you calling AsyncTaskExecutor.submit(...) from within the "
+ + "onPreExecute() method of another task being submitted?\n"
+ + "Sorry! Not that's not supported.", task);
+ mNextTask = task;
+ }
+ return task.executeOnExecutor(this, params);
+ }
+ }
+
+ @Override
+ public <T> AsyncTask<T, ?, ?> submit(Object identifier, AsyncTask<T, ?, ?> task, T... params) {
+ AsyncTaskExecutors.checkCalledFromUiThread();
+ return mBlockingExecutor.submit(identifier, task, params);
+ }
+
+ /**
+ * Runs a single task matching the given identifier.
+ * <p>
+ * Removes the matching task from the list of submitted tasks, then runs it. The executor used
+ * to execute this async task will be a same-thread executor.
+ * <p>
+ * Fails if there was not exactly one task matching the given identifier.
+ * <p>
+ * This method blocks until the AsyncTask has completely finished executing.
+ */
+ public void runTask(Object identifier) throws InterruptedException {
+ List<SubmittedTask> tasks = getSubmittedTasksByIdentifier(identifier, true);
+ Assert.assertEquals("Expected one task " + identifier + ", got " + tasks, 1, tasks.size());
+ runTask(tasks.get(0));
+ }
+
+ /**
+ * Runs all tasks whose identifier matches the given identifier.
+ * <p>
+ * Removes all matching tasks from the list of submitted tasks, and runs them. The executor used
+ * to execute these async tasks will be a same-thread executor.
+ * <p>
+ * Fails if there were no tasks matching the given identifier.
+ * <p>
+ * This method blocks until the AsyncTask objects have completely finished executing.
+ */
+ public void runAllTasks(Object identifier) throws InterruptedException {
+ List<SubmittedTask> tasks = getSubmittedTasksByIdentifier(identifier, true);
+ Assert.assertTrue("There were no tasks with identifier " + identifier, tasks.size() > 0);
+ for (SubmittedTask task : tasks) {
+ runTask(task);
+ }
+ }
+
+ /**
+ * Executes a single {@link SubmittedTask}.
+ * <p>
+ * Blocks until the task has completed running.
+ */
+ private <T> void runTask(final SubmittedTask submittedTask) throws InterruptedException {
+ submittedTask.getRunnable().run();
+ // Block until the onPostExecute or onCancelled has finished.
+ // Unfortunately we can't be sure when the AsyncTask will have posted its result handling
+ // code to the main ui thread, the best we can do is wait for the Status to be FINISHED.
+ final CountDownLatch latch = new CountDownLatch(1);
+ class AsyncTaskHasFinishedRunnable implements Runnable {
+ @Override
+ public void run() {
+ if (submittedTask.getAsyncTask().getStatus() == AsyncTask.Status.FINISHED) {
+ latch.countDown();
+ } else {
+ mInstrumentation.waitForIdle(this);
+ }
+ }
+ }
+ mInstrumentation.waitForIdle(new AsyncTaskHasFinishedRunnable());
+ Assert.assertTrue(latch.await(mTimeoutMs, TimeUnit.MILLISECONDS));
+ }
+
+ private List<SubmittedTask> getSubmittedTasksByIdentifier(
+ Object identifier, boolean remove) {
+ Assert.assertNotNull(identifier);
+ List<SubmittedTask> results = Lists.newArrayList();
+ synchronized (mLock) {
+ Iterator<SubmittedTask> iter = mSubmittedTasks.iterator();
+ while (iter.hasNext()) {
+ SubmittedTask task = iter.next();
+ if (identifier.equals(task.getIdentifier())) {
+ results.add(task);
+ iter.remove();
+ }
+ }
+ }
+ return results;
+ }
+
+ /** Get a factory that will return this instance - useful for testing. */
+ public AsyncTaskExecutors.AsyncTaskExecutorFactory getFactory() {
+ return new AsyncTaskExecutors.AsyncTaskExecutorFactory() {
+ @Override
+ public AsyncTaskExecutor createAsyncTaskExeuctor() {
+ return FakeAsyncTaskExecutor.this;
+ }
+ };
+ }
+}
diff --git a/tests/src/com/android/dialer/util/LocaleTestUtils.java b/tests/src/com/android/dialer/util/LocaleTestUtils.java
new file mode 100644
index 000000000..b893ccb76
--- /dev/null
+++ b/tests/src/com/android/dialer/util/LocaleTestUtils.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.util;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+
+import java.util.Locale;
+
+/**
+ * Utility class to save and restore the locale of the system.
+ * <p>
+ * This can be used for tests that assume to be run in a certain locale, e.g., because they
+ * check against strings in a particular language or require an assumption on how the system
+ * will behave in a specific locale.
+ * <p>
+ * In your test, you can change the locale with the following code:
+ * <pre>
+ * public class CanadaFrenchTest extends AndroidTestCase {
+ * private LocaleTestUtils mLocaleTestUtils;
+ *
+ * &#64;Override
+ * public void setUp() throws Exception {
+ * super.setUp();
+ * mLocaleTestUtils = new LocaleTestUtils(getContext());
+ * mLocaleTestUtils.setLocale(Locale.CANADA_FRENCH);
+ * }
+ *
+ * &#64;Override
+ * public void tearDown() throws Exception {
+ * mLocaleTestUtils.restoreLocale();
+ * mLocaleTestUtils = null;
+ * super.tearDown();
+ * }
+ *
+ * ...
+ * }
+ * </pre>
+ * Note that one should not call {@link #setLocale(Locale)} more than once without calling
+ * {@link #restoreLocale()} first.
+ * <p>
+ * This class is not thread-safe. Usually its methods should be invoked only from the test thread.
+ */
+public class LocaleTestUtils {
+ private final Context mContext;
+ private boolean mSaved;
+ private Locale mSavedContextLocale;
+ private Locale mSavedSystemLocale;
+
+ /**
+ * Create a new instance that can be used to set and reset the locale for the given context.
+ *
+ * @param context the context on which to alter the locale
+ */
+ public LocaleTestUtils(Context context) {
+ mContext = context;
+ mSaved = false;
+ }
+
+ /**
+ * Set the locale to the given value and saves the previous value.
+ *
+ * @param locale the value to which the locale should be set
+ * @throws IllegalStateException if the locale was already set
+ */
+ public void setLocale(Locale locale) {
+ if (mSaved) {
+ throw new IllegalStateException(
+ "call restoreLocale() before calling setLocale() again");
+ }
+ mSavedContextLocale = setResourcesLocale(mContext.getResources(), locale);
+ mSavedSystemLocale = setResourcesLocale(Resources.getSystem(), locale);
+ mSaved = true;
+ }
+
+ /**
+ * Restores the previously set locale.
+ *
+ * @throws IllegalStateException if the locale was not set using {@link #setLocale(Locale)}
+ */
+ public void restoreLocale() {
+ if (!mSaved) {
+ throw new IllegalStateException("call setLocale() before calling restoreLocale()");
+ }
+ setResourcesLocale(mContext.getResources(), mSavedContextLocale);
+ setResourcesLocale(Resources.getSystem(), mSavedSystemLocale);
+ mSaved = false;
+ }
+
+ /**
+ * Sets the locale for the given resources and returns the previous locale.
+ *
+ * @param resources the resources on which to set the locale
+ * @param locale the value to which to set the locale
+ * @return the previous value of the locale for the resources
+ */
+ private Locale setResourcesLocale(Resources resources, Locale locale) {
+ Configuration contextConfiguration = new Configuration(resources.getConfiguration());
+ Locale savedLocale = contextConfiguration.locale;
+ contextConfiguration.locale = locale;
+ resources.updateConfiguration(contextConfiguration, null);
+ return savedLocale;
+ }
+}
diff --git a/tests/src/com/android/dialer/voicemail/VoicemailStatusHelperImplTest.java b/tests/src/com/android/dialer/voicemail/VoicemailStatusHelperImplTest.java
new file mode 100644
index 000000000..2e75d1d01
--- /dev/null
+++ b/tests/src/com/android/dialer/voicemail/VoicemailStatusHelperImplTest.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.voicemail;
+
+import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE;
+import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_CAN_BE_CONFIGURED;
+import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_NOT_CONFIGURED;
+import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE;
+import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_NO_CONNECTION;
+import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_OK;
+import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE;
+import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING;
+import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION;
+import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.VoicemailContract.Status;
+import android.test.AndroidTestCase;
+
+import com.android.contacts.R;
+import com.android.dialer.voicemail.VoicemailStatusHelper.StatusMessage;
+
+import java.util.List;
+
+/**
+ * Unit tests for {@link VoicemailStatusHelperImpl}.
+ */
+public class VoicemailStatusHelperImplTest extends AndroidTestCase {
+ private static final String[] TEST_PACKAGES = new String[] {
+ "com.test.package1",
+ "com.test.package2"
+ };
+
+ private static final Uri TEST_SETTINGS_URI = Uri.parse("http://www.visual.voicemail.setup");
+ private static final Uri TEST_VOICEMAIL_URI = Uri.parse("tel:901");
+
+ private static final int ACTION_MSG_CALL_VOICEMAIL =
+ R.string.voicemail_status_action_call_server;
+ private static final int ACTION_MSG_CONFIGURE = R.string.voicemail_status_action_configure;
+
+ private static final int STATUS_MSG_NONE = -1;
+ private static final int STATUS_MSG_VOICEMAIL_NOT_AVAILABLE =
+ R.string.voicemail_status_voicemail_not_available;
+ private static final int STATUS_MSG_AUDIO_NOT_AVAIALABLE =
+ R.string.voicemail_status_audio_not_available;
+ private static final int STATUS_MSG_MESSAGE_WAITING = R.string.voicemail_status_messages_waiting;
+ private static final int STATUS_MSG_INVITE_FOR_CONFIGURATION =
+ R.string.voicemail_status_configure_voicemail;
+
+ // Object under test.
+ private VoicemailStatusHelper mStatusHelper;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mStatusHelper = new VoicemailStatusHelperImpl();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ for (String sourcePackage : TEST_PACKAGES) {
+ deleteEntryForPackage(sourcePackage);
+ }
+ // Set member variables to null so that they are garbage collected across different runs
+ // of the tests.
+ mStatusHelper = null;
+ super.tearDown();
+ }
+
+
+ public void testNoStatusEntries() {
+ assertEquals(0, getStatusMessages().size());
+ }
+
+ public void testAllOK() {
+ insertEntryForPackage(TEST_PACKAGES[0], getAllOkStatusValues());
+ insertEntryForPackage(TEST_PACKAGES[1], getAllOkStatusValues());
+ assertEquals(0, getStatusMessages().size());
+ }
+
+ public void testNotAllOKForOnePackage() {
+ insertEntryForPackage(TEST_PACKAGES[0], getAllOkStatusValues());
+ insertEntryForPackage(TEST_PACKAGES[1], getAllOkStatusValues());
+
+ ContentValues values = new ContentValues();
+ // Good data channel + no notification
+ // action: call voicemail
+ // msg: voicemail not available in call log page & none in call details page.
+ values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
+ values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_OK);
+ updateEntryForPackage(TEST_PACKAGES[1], values);
+ checkExpectedMessage(TEST_PACKAGES[1], values, STATUS_MSG_VOICEMAIL_NOT_AVAILABLE,
+ STATUS_MSG_NONE, ACTION_MSG_CALL_VOICEMAIL);
+
+ // Message waiting + good data channel - no action.
+ values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING);
+ values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_OK);
+ updateEntryForPackage(TEST_PACKAGES[1], values);
+ checkNoMessages(TEST_PACKAGES[1], values);
+
+ // No data channel + no notification
+ // action: call voicemail
+ // msg: voicemail not available in call log page & audio not available in call details page.
+ values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_OK);
+ values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);
+ updateEntryForPackage(TEST_PACKAGES[1], values);
+ checkExpectedMessage(TEST_PACKAGES[1], values, STATUS_MSG_VOICEMAIL_NOT_AVAILABLE,
+ STATUS_MSG_AUDIO_NOT_AVAIALABLE, ACTION_MSG_CALL_VOICEMAIL);
+
+ // No data channel + Notification OK
+ // action: call voicemail
+ // msg: voicemail not available in call log page & audio not available in call details page.
+ values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
+ values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);
+ updateEntryForPackage(TEST_PACKAGES[1], values);
+ checkExpectedMessage(TEST_PACKAGES[1], values, STATUS_MSG_VOICEMAIL_NOT_AVAILABLE,
+ STATUS_MSG_AUDIO_NOT_AVAIALABLE, ACTION_MSG_CALL_VOICEMAIL);
+
+ // No data channel + Notification OK
+ // action: call voicemail
+ // msg: message waiting in call log page & audio not available in call details page.
+ values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING);
+ values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);
+ updateEntryForPackage(TEST_PACKAGES[1], values);
+ checkExpectedMessage(TEST_PACKAGES[1], values, STATUS_MSG_MESSAGE_WAITING,
+ STATUS_MSG_AUDIO_NOT_AVAIALABLE, ACTION_MSG_CALL_VOICEMAIL);
+
+ // Not configured. No user action, so no message.
+ values.put(CONFIGURATION_STATE, CONFIGURATION_STATE_NOT_CONFIGURED);
+ updateEntryForPackage(TEST_PACKAGES[1], values);
+ checkNoMessages(TEST_PACKAGES[1], values);
+
+ // Can be configured - invite user for configure voicemail.
+ values.put(CONFIGURATION_STATE, CONFIGURATION_STATE_CAN_BE_CONFIGURED);
+ updateEntryForPackage(TEST_PACKAGES[1], values);
+ checkExpectedMessage(TEST_PACKAGES[1], values, STATUS_MSG_INVITE_FOR_CONFIGURATION,
+ STATUS_MSG_NONE, ACTION_MSG_CONFIGURE, TEST_SETTINGS_URI);
+ }
+
+ // Test that priority of messages are handled well.
+ public void testMessageOrdering() {
+ insertEntryForPackage(TEST_PACKAGES[0], getAllOkStatusValues());
+ insertEntryForPackage(TEST_PACKAGES[1], getAllOkStatusValues());
+
+ final ContentValues valuesNoNotificationGoodDataChannel = new ContentValues();
+ valuesNoNotificationGoodDataChannel.put(NOTIFICATION_CHANNEL_STATE,
+ NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
+ valuesNoNotificationGoodDataChannel.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_OK);
+
+ final ContentValues valuesNoNotificationNoDataChannel = new ContentValues();
+ valuesNoNotificationNoDataChannel.put(NOTIFICATION_CHANNEL_STATE,
+ NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
+ valuesNoNotificationNoDataChannel.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);
+
+ // Package1 with valuesNoNotificationGoodDataChannel and
+ // package2 with valuesNoNotificationNoDataChannel. Package2 should be above.
+ updateEntryForPackage(TEST_PACKAGES[0], valuesNoNotificationGoodDataChannel);
+ updateEntryForPackage(TEST_PACKAGES[1], valuesNoNotificationNoDataChannel);
+ List<StatusMessage> messages = getStatusMessages();
+ assertEquals(2, messages.size());
+ assertEquals(TEST_PACKAGES[0], messages.get(1).sourcePackage);
+ assertEquals(TEST_PACKAGES[1], messages.get(0).sourcePackage);
+
+ // Now reverse the values - ordering should be reversed as well.
+ updateEntryForPackage(TEST_PACKAGES[0], valuesNoNotificationNoDataChannel);
+ updateEntryForPackage(TEST_PACKAGES[1], valuesNoNotificationGoodDataChannel);
+ messages = getStatusMessages();
+ assertEquals(2, messages.size());
+ assertEquals(TEST_PACKAGES[0], messages.get(0).sourcePackage);
+ assertEquals(TEST_PACKAGES[1], messages.get(1).sourcePackage);
+ }
+
+ /** Checks that the expected source status message is returned by VoicemailStatusHelper. */
+ private void checkExpectedMessage(String sourcePackage, ContentValues values,
+ int expectedCallLogMsg, int expectedCallDetailsMsg, int expectedActionMsg,
+ Uri expectedUri) {
+ List<StatusMessage> messages = getStatusMessages();
+ assertEquals(1, messages.size());
+ checkMessageMatches(messages.get(0), sourcePackage, expectedCallLogMsg,
+ expectedCallDetailsMsg, expectedActionMsg, expectedUri);
+ }
+
+ private void checkExpectedMessage(String sourcePackage, ContentValues values,
+ int expectedCallLogMsg, int expectedCallDetailsMessage, int expectedActionMsg) {
+ checkExpectedMessage(sourcePackage, values, expectedCallLogMsg, expectedCallDetailsMessage,
+ expectedActionMsg, TEST_VOICEMAIL_URI);
+ }
+
+ private void checkMessageMatches(StatusMessage message, String expectedSourcePackage,
+ int expectedCallLogMsg, int expectedCallDetailsMsg, int expectedActionMsg,
+ Uri expectedUri) {
+ assertEquals(expectedSourcePackage, message.sourcePackage);
+ assertEquals(expectedCallLogMsg, message.callLogMessageId);
+ assertEquals(expectedCallDetailsMsg, message.callDetailsMessageId);
+ assertEquals(expectedActionMsg, message.actionMessageId);
+ if (expectedUri == null) {
+ assertNull(message.actionUri);
+ } else {
+ assertEquals(expectedUri, message.actionUri);
+ }
+ }
+
+ private void checkNoMessages(String sourcePackage, ContentValues values) {
+ assertEquals(1, updateEntryForPackage(sourcePackage, values));
+ List<StatusMessage> messages = getStatusMessages();
+ assertEquals(0, messages.size());
+ }
+
+ private ContentValues getAllOkStatusValues() {
+ ContentValues values = new ContentValues();
+ values.put(Status.SETTINGS_URI, TEST_SETTINGS_URI.toString());
+ values.put(Status.VOICEMAIL_ACCESS_URI, TEST_VOICEMAIL_URI.toString());
+ values.put(Status.CONFIGURATION_STATE, Status.CONFIGURATION_STATE_OK);
+ values.put(Status.DATA_CHANNEL_STATE, Status.DATA_CHANNEL_STATE_OK);
+ values.put(Status.NOTIFICATION_CHANNEL_STATE, Status.NOTIFICATION_CHANNEL_STATE_OK);
+ return values;
+ }
+
+ private void insertEntryForPackage(String sourcePackage, ContentValues values) {
+ // If insertion fails then try update as the record might already exist.
+ if (getContentResolver().insert(Status.buildSourceUri(sourcePackage), values) == null) {
+ updateEntryForPackage(sourcePackage, values);
+ }
+ }
+
+ private void deleteEntryForPackage(String sourcePackage) {
+ getContentResolver().delete(Status.buildSourceUri(sourcePackage), null, null);
+ }
+
+ private int updateEntryForPackage(String sourcePackage, ContentValues values) {
+ return getContentResolver().update(
+ Status.buildSourceUri(sourcePackage), values, null, null);
+ }
+
+ private List<StatusMessage> getStatusMessages() {
+ // Restrict the cursor to only the the test packages to eliminate any side effects if there
+ // are other status messages already stored on the device.
+ Cursor cursor = getContentResolver().query(Status.CONTENT_URI,
+ VoicemailStatusHelperImpl.PROJECTION, getTestPackageSelection(), null, null);
+ return mStatusHelper.getStatusMessages(cursor);
+ }
+
+ private String getTestPackageSelection() {
+ StringBuilder sb = new StringBuilder();
+ for (String sourcePackage : TEST_PACKAGES) {
+ if (sb.length() > 0) {
+ sb.append(" OR ");
+ }
+ sb.append(String.format("(source_package='%s')", sourcePackage));
+ }
+ return sb.toString();
+ }
+
+ private ContentResolver getContentResolver() {
+ return getContext().getContentResolver();
+ }
+}