Merge "Revert "Prepatory CallDetailActivity tweaks."" into ub-contactsdialer-a-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e58d2ce..94333eb 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -244,6 +244,12 @@
         <activity android:name="com.android.contacts.common.vcard.ExportVCardActivity"
                   android:theme="@style/BackgroundOnlyTheme"/>
 
+        <activity
+            android:name="com.android.dialer.onboard.OnboardingActivity"
+            android:theme="@style/OnboardingFlowTheme"
+            android:screenOrientation="nosensor"
+            android:exported="false" />
+
         <service
             android:name="com.android.contacts.common.vcard.VCardService"
             android:exported="false"/>
diff --git a/res/layout/onboarding_activity.xml b/res/layout/onboarding_activity.xml
new file mode 100644
index 0000000..a893ce4
--- /dev/null
+++ b/res/layout/onboarding_activity.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/onboarding_fragment_container">
+</FrameLayout>
diff --git a/res/layout/onboarding_screen_fragment.xml b/res/layout/onboarding_screen_fragment.xml
new file mode 100644
index 0000000..f4136ae
--- /dev/null
+++ b/res/layout/onboarding_screen_fragment.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="20dp" >
+
+    <TextView
+        android:id="@+id/onboarding_screen_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_above="@+id/onboarding_screen_content"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:textColor="@color/onboarding_primary_text_color" />
+
+    <TextView
+        android:id="@id/onboarding_screen_content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_above="@+id/onboarding_buttons_container"
+        android:layout_marginTop="20dp"
+        android:textColor="@color/onboarding_primary_text_color" />
+
+    <LinearLayout
+        android:id="@id/onboarding_buttons_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_marginTop="20dp" >
+
+        <Button
+            android:id="@+id/onboard_skip_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:text="@string/onboarding_skip_button"
+            android:textColor="@color/onboarding_primary_text_color"
+            style="?android:attr/borderlessButtonStyle" />
+
+        <Button
+            android:id="@+id/onboard_next_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:text="@string/onboarding_next_button"
+            android:textColor="@color/onboarding_primary_text_color"
+            style="?android:attr/borderlessButtonStyle" />
+
+    </LinearLayout>
+</RelativeLayout>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index a747927..e80ee69 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -111,4 +111,10 @@
     <color name="delete_icon_tint">#6D6D6D</color>
     <color name="blocked_number_background">#E0E0E0</color>
     <color name="blocked_number_accent_color">#42A5F5</color>
+    <color name="blocked_number_block_color">#F44336</color>
+
+    <!-- Colors for onboarding flow -->
+    <color name="onboarding_primary_text_color">#ffffff</color>
+    <color name="onboarding_default_dialer_screen_background_color">#e06055</color>
+    <color name="onboarding_permissions_screen_background_color">#689f38</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index b36066a..0a330d8 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -153,5 +153,5 @@
     <dimen name="blocked_number_primary_text_size">16sp</dimen>
     <dimen name="blocked_number_secondary_text_size">12sp</dimen>
     <dimen name="blocked_number_delete_icon_size">32dp</dimen>
-    <dimen name="blocked_number_search_text_size">16sp</dimen>
+    <dimen name="blocked_number_search_text_size">14sp</dimen>
 </resources>
diff --git a/res/values/ids.xml b/res/values/ids.xml
index 0034fe3..f96761a 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -20,4 +20,5 @@
     <item type="id" name="context_menu_edit_before_call" />
     <item type="id" name="context_menu_block_number" />
     <item type="id" name="settings_header_sounds_and_vibration" />
+    <item type="id" name="block_id" />
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c8b238f..d51ff6d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -309,13 +309,13 @@
          is already in progress.) -->
     <string name="dialer_addAnotherCall">Add call</string>
 
-    <!-- Title for incoming call details screen -->
+    <!-- Title for incoming call type. [CHAR LIMIT=40] -->
     <string name="type_incoming">Incoming call</string>
 
-    <!-- Title for outgoing call details screen -->
+    <!-- Title for outgoing call type. [CHAR LIMIT=40] -->
     <string name="type_outgoing">Outgoing call</string>
 
-    <!-- Title for missed call details screen -->
+    <!-- Title for missed call type. [CHAR LIMIT=40] -->
     <string name="type_missed">Missed call</string>
 
     <!-- Title for incoming video call in call details screen [CHAR LIMIT=60] -->
@@ -330,6 +330,12 @@
     <!-- Title for voicemail details screen -->
     <string name="type_voicemail">Voicemail</string>
 
+    <!-- Title for rejected call type. [CHAR LIMIT=40] -->
+    <string name="type_rejected">Declined call</string>
+
+    <!-- Title for blocked call type. [CHAR LIMIT=40] -->
+    <string name="type_blocked">Blocked call</string>
+
     <!-- Description for incoming calls going to voice mail vs. not -->
     <string name="actionIncomingCall">Incoming calls</string>
 
@@ -424,6 +430,15 @@
     <!-- A nicely formatted call duration displayed when viewing call details. For example "42 min 28 sec" -->
     <string name="callDetailsDurationFormat"><xliff:g id="minutes" example="42">%s</xliff:g> min <xliff:g id="seconds" example="28">%s</xliff:g> sec</string>
 
+    <!-- The string 'Today'. This value is used in the voicemailCallLogDateTimeFormat rather than an
+         explicit date string, e.g. Jul 25, 2014, in the event that a voicemail was created on the
+         current day -->
+    <string name="voicemailCallLogToday">@string/call_log_header_today</string>
+
+    <!-- A format string used for displaying the date and time for a voicemail call log. For example: Jul 25, 2014 at 2:49 PM
+         The date will be replaced by 'Today' for voicemails created on the current day. For example: Today at 2:49 PM -->
+    <string name="voicemailCallLogDateTimeFormat"><xliff:g id="date" example="Jul 25, 2014">%1$s</xliff:g> at <xliff:g id="time" example="2:49 PM">%2$s</xliff:g></string>
+
     <!-- Dialog message which is shown when the user tries to make a phone call
          to prohibited phone numbers [CHAR LIMIT=NONE] -->
     <string name="dialog_phone_call_prohibited_message" msgid="4313552620858880999">Can\'t call this number</string>
@@ -471,7 +486,7 @@
     <string name="dialer_hint_find_contact">Search contacts</string>
 
     <!-- Hint displayed in add blocked number search box when there is no query typed.
-         [CHAR LIMIT=30] -->
+         [CHAR LIMIT=40] -->
     <string name="block_number_search_hint">Add number or search contacts</string>
 
     <!-- String resource for the font-family to use for the call log activity's title
@@ -884,4 +899,22 @@
 
     <!-- Shown as a message that notifies the user that the Phone app cannot write to system settings, which is why the system settings app is being launched directly instead.-->
     <string name="toast_cannot_write_system_settings">Phone app does not have permission to write to system settings.</string>
+
+    <!-- Title of the onboarding screen that asks the user to make Phone the default Phone app -->
+    <string name="request_default_dialer_screen_title">A better way of calling is calling</string>
+
+    <!-- Content of the onboarding screen that asks the user to make Phone the default Phone app -->
+    <string name="request_default_dialer_screen_content">Make Phone your default phone app to be able to do things like see who\'s calling you, even when they\'re not in your contacts.</string>
+
+    <!-- Title of the onboarding screen that asks the user to grant us the Contacts and Phone permissions -->
+    <string name="request_permissions_screen_title">Get talking to your friends and family</string>
+
+    <!-- Content of the onboarding screen that asks the user to grant us the Contacts and Phone permissions -->
+    <string name="request_permissions_screen_content">Phone will need to access your phone and contacts to make calls to people in your contacts.</string>
+
+    <!-- The label of the button used to skip a screen in the onboarding flow -->
+    <string name="onboarding_skip_button">Skip</string>
+
+    <!-- The label of the button used to go to the next screen in the onboarding flow -->
+    <string name="onboarding_next_button">Next</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 17b4a25..86c3ad4 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -109,6 +109,13 @@
         <item name="actionOverflowButtonStyle">@style/DialtactsActionBarOverflowWhite</item>
     </style>
 
+    <style name="OnboardingFlowTheme" parent="DialtactsThemeWithoutActionBarOverlay">
+        <item name="android:windowActionBar">false</item>
+        <item name="windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+
     <!-- Hide the actionbar title during the activity preview -->
     <style name="DialtactsActivityTheme" parent="DialtactsTheme">
         <!-- Styles that require AppCompat compatibility, remember to update both sets -->
diff --git a/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java b/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java
index aa994d2..89932ff 100644
--- a/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java
+++ b/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java
@@ -31,6 +31,7 @@
 import com.android.contacts.common.GeoUtil;
 import com.android.dialer.DialtactsActivity;
 import com.android.dialer.PhoneCallDetails;
+import com.android.dialer.util.AppCompatConstants;
 import com.android.dialer.util.AsyncTaskExecutor;
 import com.android.dialer.util.AsyncTaskExecutors;
 import com.android.dialer.util.PhoneNumberUtil;
@@ -45,6 +46,7 @@
     public enum Tasks {
         DELETE_VOICEMAIL,
         DELETE_CALL,
+        MARK_BLOCKED,
         MARK_VOICEMAIL_READ,
         GET_CALL_DETAILS,
     }
@@ -79,12 +81,31 @@
         static final int TRANSCRIPTION_COLUMN_INDEX = 11;
     }
 
+    private static class CallLogMarkBlockedQuery {
+        static final String[] PROJECTION = new String[] {
+            CallLog.Calls._ID,
+            CallLog.Calls.DATE
+        };
+
+        static final int ID_COLUMN_INDEX = 0;
+        static final int DATE_COLUMN_INDEX = 1;
+    }
+
     public interface CallLogAsyncTaskListener {
         public void onDeleteCall();
         public void onDeleteVoicemail();
         public void onGetCallDetails(PhoneCallDetails[] details);
     }
 
+    public interface OnCallLogQueryFinishedListener {
+        public void onQueryFinished(boolean hasEntry);
+    }
+
+    // Try to identify if a call log entry corresponds to a number which was blocked. We match by
+    // by comparing its creation time to the time it was added in the InCallUi and seeing if they
+    // fall within a certain threshold.
+    private static final int MATCH_BLOCKED_CALL_THRESHOLD_MS = 1500;
+
     private static AsyncTaskExecutor sAsyncTaskExecutor;
 
     private static void initTaskExecutor() {
@@ -156,8 +177,8 @@
             boolean isVoicemail = PhoneNumberUtil.isVoicemailNumber(context, accountHandle, number);
             boolean shouldLookupNumber =
                     PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation) && !isVoicemail;
-
             ContactInfo info = ContactInfo.EMPTY;
+
             if (shouldLookupNumber) {
                 ContactInfo lookupInfo = contactInfoHelper.lookupNumber(number, countryIso);
                 info = lookupInfo != null ? lookupInfo : ContactInfo.EMPTY;
@@ -205,7 +226,7 @@
      *
      * @param context The context.
      * @param callIds String of the callIds to delete from the call log, delimited by commas (",").
-     * @param callLogAsyncTaskListenerg The listener to invoke after the entries have been deleted.
+     * @param callLogAsyncTaskListener The listener to invoke after the entries have been deleted.
      */
     public static void deleteCalls(
             final Context context,
@@ -215,26 +236,88 @@
             initTaskExecutor();
         }
 
-        sAsyncTaskExecutor.submit(Tasks.DELETE_CALL,
-                new AsyncTask<Void, Void, Void>() {
-                    @Override
-                    public Void doInBackground(Void... params) {
-                        context.getContentResolver().delete(
-                                TelecomUtil.getCallLogUri(context),
-                                CallLog.Calls._ID + " IN (" + callIds + ")", null);
-                        return null;
-                    }
+        sAsyncTaskExecutor.submit(Tasks.DELETE_CALL, new AsyncTask<Void, Void, Void>() {
+            @Override
+            public Void doInBackground(Void... params) {
+                context.getContentResolver().delete(
+                        TelecomUtil.getCallLogUri(context),
+                        CallLog.Calls._ID + " IN (" + callIds + ")", null);
+                return null;
+            }
 
-                    @Override
-                    public void onPostExecute(Void result) {
-                        if (callLogAsyncTaskListener != null) {
-                            callLogAsyncTaskListener.onDeleteCall();
-                        }
-                    }
-                });
-
+            @Override
+            public void onPostExecute(Void result) {
+                if (callLogAsyncTaskListener != null) {
+                    callLogAsyncTaskListener.onDeleteCall();
+                }
+            }
+        });
     }
 
+    /**
+     * Marks last call made by the number the call type of the specified call as BLOCKED in the call log.
+     *
+     * @param context The context.
+     * @param number Number of which to mark the most recent call as BLOCKED.
+     * @param timeAddedMs The time the number was added to InCall, in milliseconds.
+     * @param listener The listener to invoke after looking up for a call log entry matching the
+     *     number and time added.
+     */
+    public static void markCallAsBlocked(
+            final Context context,
+            final String number,
+            final long timeAddedMs,
+            final OnCallLogQueryFinishedListener listener) {
+        if (sAsyncTaskExecutor == null) {
+            initTaskExecutor();
+        }
+
+        sAsyncTaskExecutor.submit(Tasks.MARK_BLOCKED, new AsyncTask<Void, Void, Long>() {
+            @Override
+            public Long doInBackground(Void... params) {
+                // First, lookup the call log entry of the most recent call with this number.
+                Cursor cursor = context.getContentResolver().query(
+                        TelecomUtil.getCallLogUri(context),
+                        CallLogMarkBlockedQuery.PROJECTION,
+                        CallLog.Calls.NUMBER + "= ?",
+                        new String[] { number },
+                        CallLog.Calls.DATE + " DESC LIMIT 1");
+
+                // If found, return the call log entry id.
+                if (cursor.moveToFirst()) {
+                    long creationTime = cursor.getLong(CallLogMarkBlockedQuery.DATE_COLUMN_INDEX);
+                    if (timeAddedMs > creationTime
+                            && timeAddedMs - creationTime < MATCH_BLOCKED_CALL_THRESHOLD_MS) {
+                        return cursor.getLong(CallLogMarkBlockedQuery.ID_COLUMN_INDEX);
+                    }
+                }
+                return (long) -1;
+            }
+
+            @Override
+            public void onPostExecute(Long callLogEntryId) {
+                if (listener != null) {
+                    listener.onQueryFinished(callLogEntryId >= 0);
+                }
+
+                if (callLogEntryId < 0) {
+                    return;
+                }
+
+                // Then, update that call log entry to have type BLOCKED.
+                final ContentValues values = new ContentValues();
+                values.put(CallLog.Calls.TYPE, AppCompatConstants.CALLS_BLOCKED_TYPE);
+
+                context.getContentResolver().update(
+                        TelecomUtil.getCallLogUri(context),
+                        values,
+                        CallLog.Calls._ID + "= ?",
+                        new String[] { Long.toString(callLogEntryId) });
+            }
+        });
+    }
+
+
     public static void markVoicemailAsRead(final Context context, final Uri voicemailUri) {
         if (sAsyncTaskExecutor == null) {
             initTaskExecutor();
@@ -266,21 +349,20 @@
             initTaskExecutor();
         }
 
-        sAsyncTaskExecutor.submit(Tasks.DELETE_VOICEMAIL,
-                new AsyncTask<Void, Void, Void>() {
-                    @Override
-                    public Void doInBackground(Void... params) {
-                        context.getContentResolver().delete(voicemailUri, null, null);
-                        return null;
-                    }
+        sAsyncTaskExecutor.submit(Tasks.DELETE_VOICEMAIL, new AsyncTask<Void, Void, Void>() {
+            @Override
+            public Void doInBackground(Void... params) {
+                context.getContentResolver().delete(voicemailUri, null, null);
+                return null;
+            }
 
-                    @Override
-                    public void onPostExecute(Void result) {
-                        if (callLogAsyncTaskListener != null) {
-                            callLogAsyncTaskListener.onDeleteVoicemail();
-                        }
-                    }
-                });
+            @Override
+            public void onPostExecute(Void result) {
+                if (callLogAsyncTaskListener != null) {
+                    callLogAsyncTaskListener.onDeleteVoicemail();
+                }
+            }
+        });
     }
 
     @VisibleForTesting
diff --git a/src/com/android/dialer/calllog/CallLogListItemHelper.java b/src/com/android/dialer/calllog/CallLogListItemHelper.java
index d18e274..8e45dd3 100644
--- a/src/com/android/dialer/calllog/CallLogListItemHelper.java
+++ b/src/com/android/dialer/calllog/CallLogListItemHelper.java
@@ -24,6 +24,7 @@
 import android.util.Log;
 
 import com.android.dialer.PhoneCallDetails;
+import com.android.dialer.util.AppCompatConstants;
 import com.android.dialer.R;
 
 /**
@@ -220,11 +221,12 @@
         int lastCallType = getLastCallType(callTypes);
         int stringID;
 
-        if (lastCallType == Calls.VOICEMAIL_TYPE || lastCallType == Calls.MISSED_TYPE) {
+        if (lastCallType == AppCompatConstants.CALLS_VOICEMAIL_TYPE
+                || lastCallType == AppCompatConstants.CALLS_MISSED_TYPE) {
             //Message: Missed call from <NameOrNumber>, <TypeOrLocation>, <TimeOfCall>,
             //<PhoneAccount>.
             stringID = R.string.description_incoming_missed_call;
-        } else if (lastCallType == Calls.INCOMING_TYPE) {
+        } else if (lastCallType == AppCompatConstants.CALLS_INCOMING_TYPE) {
             //Message: Answered call from <NameOrNumber>, <TypeOrLocation>, <TimeOfCall>,
             //<PhoneAccount>.
             stringID = R.string.description_incoming_answered_call;
diff --git a/src/com/android/dialer/calllog/CallLogQueryHandler.java b/src/com/android/dialer/calllog/CallLogQueryHandler.java
index 771df99..81e49d2 100644
--- a/src/com/android/dialer/calllog/CallLogQueryHandler.java
+++ b/src/com/android/dialer/calllog/CallLogQueryHandler.java
@@ -36,6 +36,7 @@
 
 import com.android.contacts.common.database.NoNullCursorAsyncQueryHandler;
 import com.android.contacts.common.util.PermissionsUtil;
+import com.android.dialer.util.AppCompatConstants;
 import com.android.dialer.util.TelecomUtil;
 import com.android.dialer.voicemail.VoicemailStatusHelperImpl;
 
@@ -180,7 +181,7 @@
             selectionArgs.add(Integer.toString(callType));
         } else {
             where.append(" AND NOT ");
-            where.append("(" + Calls.TYPE + " = " + Calls.VOICEMAIL_TYPE + ")");
+            where.append("(" + Calls.TYPE + " = " + AppCompatConstants.CALLS_VOICEMAIL_TYPE + ")");
         }
 
         if (newerThan > 0) {
diff --git a/src/com/android/dialer/calllog/CallTypeHelper.java b/src/com/android/dialer/calllog/CallTypeHelper.java
index 36c0975..acc114c 100644
--- a/src/com/android/dialer/calllog/CallTypeHelper.java
+++ b/src/com/android/dialer/calllog/CallTypeHelper.java
@@ -17,9 +17,9 @@
 package com.android.dialer.calllog;
 
 import android.content.res.Resources;
-import android.provider.CallLog.Calls;
 
 import com.android.dialer.R;
+import com.android.dialer.util.AppCompatConstants;
 
 /**
  * Helper class to perform operations related to call types.
@@ -39,6 +39,10 @@
     private final CharSequence mMissedVideoName;
     /** Name used to identify voicemail calls. */
     private final CharSequence mVoicemailName;
+    /** Name used to identify rejected calls. */
+    private final CharSequence mRejectedName;
+    /** Name used to identify blocked calls. */
+    private final CharSequence mBlockedName;
     /** Color used to identify new missed calls. */
     private final int mNewMissedColor;
     /** Color used to identify new voicemail calls. */
@@ -53,6 +57,8 @@
         mOutgoingVideoName = resources.getString(R.string.type_outgoing_video);
         mMissedVideoName = resources.getString(R.string.type_missed_video);
         mVoicemailName = resources.getString(R.string.type_voicemail);
+        mRejectedName = resources.getString(R.string.type_rejected);
+        mBlockedName = resources.getString(R.string.type_blocked);
         mNewMissedColor = resources.getColor(R.color.call_log_missed_call_highlight_color);
         mNewVoicemailColor = resources.getColor(R.color.call_log_voicemail_highlight_color);
     }
@@ -60,30 +66,36 @@
     /** Returns the text used to represent the given call type. */
     public CharSequence getCallTypeText(int callType, boolean isVideoCall) {
         switch (callType) {
-            case Calls.INCOMING_TYPE:
+            case AppCompatConstants.CALLS_INCOMING_TYPE:
                 if (isVideoCall) {
                     return mIncomingVideoName;
                 } else {
                     return mIncomingName;
                 }
 
-            case Calls.OUTGOING_TYPE:
+            case AppCompatConstants.CALLS_OUTGOING_TYPE:
                 if (isVideoCall) {
                     return mOutgoingVideoName;
                 } else {
                     return mOutgoingName;
                 }
 
-            case Calls.MISSED_TYPE:
+            case AppCompatConstants.CALLS_MISSED_TYPE:
                 if (isVideoCall) {
                     return mMissedVideoName;
                 } else {
                     return mMissedName;
                 }
 
-            case Calls.VOICEMAIL_TYPE:
+            case AppCompatConstants.CALLS_VOICEMAIL_TYPE:
                 return mVoicemailName;
 
+            case AppCompatConstants.CALLS_REJECTED_TYPE:
+                return mRejectedName;
+
+            case AppCompatConstants.CALLS_BLOCKED_TYPE:
+                return mBlockedName;
+
             default:
                 return mMissedName;
         }
@@ -92,18 +104,18 @@
     /** Returns the color used to highlight the given call type, null if not highlight is needed. */
     public Integer getHighlightedColor(int callType) {
         switch (callType) {
-            case Calls.INCOMING_TYPE:
+            case AppCompatConstants.CALLS_INCOMING_TYPE:
                 // New incoming calls are not highlighted.
                 return null;
 
-            case Calls.OUTGOING_TYPE:
+            case AppCompatConstants.CALLS_OUTGOING_TYPE:
                 // New outgoing calls are not highlighted.
                 return null;
 
-            case Calls.MISSED_TYPE:
+            case AppCompatConstants.CALLS_MISSED_TYPE:
                 return mNewMissedColor;
 
-            case Calls.VOICEMAIL_TYPE:
+            case AppCompatConstants.CALLS_VOICEMAIL_TYPE:
                 return mNewVoicemailColor;
 
             default:
@@ -115,7 +127,8 @@
     }
 
     public static boolean isMissedCallType(int callType) {
-        return (callType != Calls.INCOMING_TYPE && callType != Calls.OUTGOING_TYPE &&
-                callType != Calls.VOICEMAIL_TYPE);
+        return (callType != AppCompatConstants.CALLS_INCOMING_TYPE
+                && callType != AppCompatConstants.CALLS_OUTGOING_TYPE
+                && callType != AppCompatConstants.CALLS_VOICEMAIL_TYPE);
     }
 }
diff --git a/src/com/android/dialer/calllog/CallTypeIconsView.java b/src/com/android/dialer/calllog/CallTypeIconsView.java
index 31d4f4b..d680cfe 100644
--- a/src/com/android/dialer/calllog/CallTypeIconsView.java
+++ b/src/com/android/dialer/calllog/CallTypeIconsView.java
@@ -23,13 +23,13 @@
 import android.graphics.PorterDuff;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.provider.CallLog.Calls;
 import android.util.AttributeSet;
 import android.view.View;
 
 import com.android.contacts.common.testing.NeededForTesting;
 import com.android.contacts.common.util.BitmapUtil;
 import com.android.dialer.R;
+import com.android.dialer.util.AppCompatConstants;
 import com.google.common.collect.Lists;
 
 import java.util.List;
@@ -106,13 +106,13 @@
 
     private Drawable getCallTypeDrawable(int callType) {
         switch (callType) {
-            case Calls.INCOMING_TYPE:
+            case AppCompatConstants.CALLS_INCOMING_TYPE:
                 return mResources.incoming;
-            case Calls.OUTGOING_TYPE:
+            case AppCompatConstants.CALLS_OUTGOING_TYPE:
                 return mResources.outgoing;
-            case Calls.MISSED_TYPE:
+            case AppCompatConstants.CALLS_MISSED_TYPE:
                 return mResources.missed;
-            case Calls.VOICEMAIL_TYPE:
+            case AppCompatConstants.CALLS_VOICEMAIL_TYPE:
                 return mResources.voicemail;
             default:
                 // It is possible for users to end up with calls with unknown call types in their
diff --git a/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java b/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java
index 54324cd..3792056 100644
--- a/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java
+++ b/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java
@@ -16,11 +16,11 @@
 
 package com.android.dialer.calllog;
 
+import com.google.common.collect.Lists;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.provider.CallLog;
 import android.provider.CallLog.Calls;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.telecom.PhoneAccount;
@@ -34,16 +34,15 @@
 import com.android.dialer.PhoneCallDetails;
 import com.android.dialer.R;
 import com.android.dialer.util.DialerUtils;
-import com.android.dialer.util.PhoneNumberUtil;
-
-import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
+import java.util.Calendar;
 
 /**
  * Helper class to fill in the views in {@link PhoneCallDetailsViews}.
  */
 public class PhoneCallDetailsHelper {
+
     /** The maximum number of icons will be shown to represent the call types in a group. */
     private static final int MAX_CALL_TYPE_ICONS = 3;
 
@@ -53,6 +52,9 @@
     private Long mCurrentTimeMillisForTest;
     private final TelecomCallLogCache mTelecomCallLogCache;
 
+    /** Calendar used to construct dates */
+    private final Calendar mCalendar;
+
     /**
      * List of items to be concatenated together for accessibility descriptions
      */
@@ -72,6 +74,7 @@
         mContext = context;
         mResources = resources;
         mTelecomCallLogCache = telecomCallLogCache;
+        mCalendar = Calendar.getInstance();
     }
 
     /** Fills the call details views with content. */
@@ -178,6 +181,7 @@
      * For a call, if there is an associated contact for the caller, return the known call type
      * (e.g. mobile, home, work).  If there is no associated contact, attempt to use the caller's
      * location if known.
+     *
      * @param details Call details to use.
      * @return Type of call (mobile/home) if known, or the location of the caller (if known).
      */
@@ -205,16 +209,62 @@
     }
 
     /**
-     * Get the call date/time of the call, relative to the current time.
-     * e.g. 3 minutes ago
+     * Get the call date/time of the call. For the call log this is relative to the current time.
+     * e.g. 3 minutes ago. For voicemail, see {@link #getGranularDateTime(PhoneCallDetails)}
+     *
      * @param details Call details to use.
      * @return String representing when the call occurred.
      */
     public CharSequence getCallDate(PhoneCallDetails details) {
-        return DateUtils.getRelativeTimeSpanString(details.date,
-                getCurrentTimeMillis(),
-                DateUtils.MINUTE_IN_MILLIS,
-                DateUtils.FORMAT_ABBREV_RELATIVE);
+        if (details.callTypes[0] == Calls.VOICEMAIL_TYPE) {
+            return getGranularDateTime(details);
+        }
+
+        return DateUtils.getRelativeTimeSpanString(details.date, getCurrentTimeMillis(),
+                DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE);
+    }
+
+    /**
+     * Get the granular version of the call date/time of the call. The result is always in the form
+     * 'DATE at TIME'. The date value changes based on when the call was created.
+     *
+     * If created today, DATE is 'Today'
+     * If created this year, DATE is 'MMM dd'
+     * Otherwise, DATE is 'MMM dd, yyyy'
+     *
+     * TIME is the localized time format, e.g. 'hh:mm a' or 'HH:mm'
+     *
+     * @param details Call details to use
+     * @return String representing when the call occurred
+     */
+    public CharSequence getGranularDateTime(PhoneCallDetails details) {
+        return mResources.getString(R.string.voicemailCallLogDateTimeFormat,
+                getGranularDate(details.date),
+                DateUtils.formatDateTime(mContext, details.date, DateUtils.FORMAT_SHOW_TIME));
+    }
+
+    /**
+     * Get the granular version of the call date. See {@link #getGranularDateTime(PhoneCallDetails)}
+     */
+    private String getGranularDate(long date) {
+        if (DateUtils.isToday(date)) {
+            return mResources.getString(R.string.voicemailCallLogToday);
+        }
+        return DateUtils.formatDateTime(mContext, date, DateUtils.FORMAT_SHOW_DATE
+                | DateUtils.FORMAT_ABBREV_MONTH
+                | (shouldShowYear(date) ? DateUtils.FORMAT_SHOW_YEAR : DateUtils.FORMAT_NO_YEAR));
+    }
+
+    /**
+     * Determines whether the year should be shown for the given date
+     *
+     * @return {@code true} if date is within the current year, {@code false} otherwise
+     */
+    private boolean shouldShowYear(long date) {
+        mCalendar.setTimeInMillis(getCurrentTimeMillis());
+        int currentYear = mCalendar.get(Calendar.YEAR);
+        mCalendar.setTimeInMillis(date);
+        return currentYear != mCalendar.get(Calendar.YEAR);
     }
 
     /** Sets the text of the header view for the details page of a phone call. */
diff --git a/src/com/android/dialer/filterednumber/BlockedNumberSearchActivity.java b/src/com/android/dialer/filterednumber/BlockedNumberSearchActivity.java
index a8cbfb3..11c0ac4 100644
--- a/src/com/android/dialer/filterednumber/BlockedNumberSearchActivity.java
+++ b/src/com/android/dialer/filterednumber/BlockedNumberSearchActivity.java
@@ -36,14 +36,14 @@
 import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
 import com.android.dialer.R;
 import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
+import com.android.dialer.list.BlockedListSearchAdapter;
 import com.android.dialer.list.OnListFragmentScrolledListener;
 import com.android.dialer.list.BlockedListSearchFragment;
 import com.android.dialer.list.SearchFragment;
 import com.android.dialer.widget.SearchEditTextLayout;
 
 public class BlockedNumberSearchActivity extends AppCompatActivity
-        implements SearchFragment.HostInterface, OnPhoneNumberPickerActionListener {
-    private static final String TAG = "BlockedNumberSearch";
+        implements SearchFragment.HostInterface {
     private static final String TAG_BLOCKED_SEARCH_FRAGMENT = "blocked_search";
 
     private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler;
@@ -115,7 +115,7 @@
             return;
         }
         final FragmentTransaction transaction = getFragmentManager().beginTransaction();
-        SearchFragment fragment = (SearchFragment) getFragmentManager()
+        BlockedListSearchFragment fragment = (BlockedListSearchFragment) getFragmentManager()
                 .findFragmentByTag(TAG_BLOCKED_SEARCH_FRAGMENT);
         if (fragment == null) {
             fragment = new BlockedListSearchFragment();
@@ -125,6 +125,8 @@
         }
         fragment.setHasOptionsMenu(false);
         fragment.setShowEmptyListForNullQuery(true);
+        fragment.setDirectorySearchEnabled(false);
+        fragment.setFilteredNumberAsyncQueryHandler(mFilteredNumberAsyncQueryHandler);
         transaction.commit();
     }
 
@@ -132,65 +134,6 @@
     public void onAttachFragment(Fragment fragment) {
         if (fragment instanceof BlockedListSearchFragment) {
             mSearchFragment = (BlockedListSearchFragment) fragment;
-            mSearchFragment.setOnPhoneNumberPickerActionListener(this);
-        }
-    }
-
-    @Override
-    public void onPickDataUri(Uri dataUri, int callInitiationType) {
-        Log.w(TAG, "onPickDataUri unsupported, ignoring.");
-    }
-
-    @Override
-    public void onPickPhoneNumber(String phoneNumber, boolean isVideoCall, int callInitiationType) {
-        blockNumber(phoneNumber);
-    }
-
-    @Override
-    public void onShortcutIntentCreated(Intent intent) {
-        Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring.");
-    }
-
-    @Override
-    public void onHomeInActionBarSelected() {
-    }
-
-    private void blockNumber(final String number) {
-        final String countryIso = GeoUtil.getCurrentCountryIso(BlockedNumberSearchActivity.this);
-        final IndeterminateProgressDialog progressDialog =
-                IndeterminateProgressDialog.show(getFragmentManager(),
-                        getString(R.string.checkingNumber, number), null, 500);
-        final String normalizedNumber =
-                FilteredNumberAsyncQueryHandler.getNormalizedNumber(number, countryIso);
-        if (normalizedNumber == null) {
-            progressDialog.dismiss();
-            Toast.makeText(
-                    BlockedNumberSearchActivity.this, getString(R.string.invalidNumber, number),
-                    Toast.LENGTH_SHORT).show();
-        } else {
-            final FilteredNumberAsyncQueryHandler.OnCheckBlockedListener onCheckListener =
-                    new FilteredNumberAsyncQueryHandler.OnCheckBlockedListener() {
-                        @Override
-                        public void onCheckComplete(Integer id) {
-                            progressDialog.dismiss();
-                            if (id == null) {
-                                final FilterNumberDialogFragment newFragment =
-                                        FilterNumberDialogFragment.newInstance(id, normalizedNumber,
-                                                number, countryIso, number);
-                                newFragment.setQueryHandler(mFilteredNumberAsyncQueryHandler);
-                                newFragment.setParentView(
-                                        findViewById(R.id.search_activity_container));
-                                newFragment.show(getFragmentManager(),
-                                        FilterNumberDialogFragment.BLOCK_DIALOG_FRAGMENT);
-                            } else {
-                                Toast.makeText(BlockedNumberSearchActivity.this,
-                                        getString(R.string.alreadyBlocked, number),
-                                        Toast.LENGTH_SHORT).show();
-                            }
-                        }
-                    };
-            mFilteredNumberAsyncQueryHandler.startBlockedQuery(
-                    onCheckListener, normalizedNumber, number, countryIso);
         }
     }
 
diff --git a/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java b/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java
index e9a88c8..1b95387 100644
--- a/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java
+++ b/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java
@@ -21,6 +21,7 @@
 import android.app.DialogFragment;
 import android.content.ContentValues;
 import android.content.DialogInterface;
+import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.design.widget.Snackbar;
@@ -28,6 +29,8 @@
 
 import com.android.dialer.R;
 import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
+import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnBlockNumberListener;
+import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnUnblockNumberListener;
 
 public class FilterNumberDialogFragment extends DialogFragment {
     public static final String BLOCK_DIALOG_FRAGMENT = "blockUnblockNumberDialog";
@@ -40,6 +43,23 @@
 
     private FilteredNumberAsyncQueryHandler mHandler;
     private View mParentView;
+    private OnBlockListener mOnBlockListener;
+    private OnUndoBlockListener mOnUndoBlockListener;
+
+    public interface OnBlockListener {
+        /**
+         * Invoked after inserting a blocked number.
+         * @param uri The uri of the newly created row.
+         */
+        public void onBlockComplete(Uri uri);
+    }
+
+    public interface OnUndoBlockListener {
+        /**
+         * Invoked on undoing the blocking of a number.
+         */
+        public void onUndoBlockComplete();
+    }
 
     public void setQueryHandler (FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler) {
         mHandler = filteredNumberAsyncQueryHandler;
@@ -49,6 +69,14 @@
         mParentView = view;
     }
 
+    public void setOnBlockListener(OnBlockListener listener) {
+        mOnBlockListener = listener;
+    }
+
+    public void setOnUndoBlockListener(OnUndoBlockListener listener) {
+        mOnUndoBlockListener = listener;
+    }
+
     public static FilterNumberDialogFragment newInstance(Integer blockId, String normalizedNumber,
         String number, String countryIso, String displayNumber) {
         final FilterNumberDialogFragment fragment = new FilterNumberDialogFragment();
@@ -95,50 +123,62 @@
         return builder.create();
     }
 
-    public void blockNumber() {
-        final String displayNumber = getArguments().getString(ARG_DISPLAY_NUMBER);
-        final String message = getString(R.string.snackbar_number_blocked, displayNumber);
-        final String undoMessage = getString(R.string.snackbar_number_unblocked, displayNumber);
-        final FilteredNumberAsyncQueryHandler.OnUnblockNumberListener undoListener =
-                new FilteredNumberAsyncQueryHandler.OnUnblockNumberListener() {
-                    @Override
-                    public void onUnblockComplete(int rows, ContentValues values) {
-                        Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show();
-                    }
-                };
+    private String getBlockedMessage(String displayNumber) {
+        return getString(R.string.snackbar_number_blocked, displayNumber);
+    }
 
-        mHandler.blockNumber(
-                new FilteredNumberAsyncQueryHandler.OnBlockNumberListener() {
-                    @Override
-                    public void onBlockComplete(final Uri uri) {
-                        Snackbar.make(mParentView, message, Snackbar.LENGTH_LONG)
-                                .setAction(R.string.block_number_undo,
-                                        // Delete the newly created row on 'undo'.
-                                        new View.OnClickListener() {
-                                            @Override
-                                            public void onClick(View view) {
-                                                mHandler.unblock(undoListener, uri);
-                                            }
-                                        })
-                                .show();
-                    }
-                }, getArguments().getString(ARG_NORMALIZED_NUMBER),
+    private String getUnblockedMessage(String displayNumber) {
+        return getString(R.string.snackbar_number_unblocked, displayNumber);
+    }
+
+    private void blockNumber() {
+        final String displayNumber = getArguments().getString(ARG_DISPLAY_NUMBER);
+        final String message = getBlockedMessage(displayNumber);
+        final String undoMessage = getUnblockedMessage(displayNumber);
+        final OnUnblockNumberListener onUndoListener = new OnUnblockNumberListener() {
+            @Override
+            public void onUnblockComplete(int rows, ContentValues values) {
+                Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show();
+                if (mOnUndoBlockListener != null) {
+                    mOnUndoBlockListener.onUndoBlockComplete();
+                }
+            }
+        };
+        final OnBlockNumberListener onBlockNumberListener = new OnBlockNumberListener() {
+            @Override
+            public void onBlockComplete(final Uri uri) {
+                Snackbar.make(mParentView, message, Snackbar.LENGTH_LONG)
+                        .setAction(R.string.block_number_undo,
+                                // Delete the newly created row on 'undo'.
+                                new View.OnClickListener() {
+                                    @Override
+                                    public void onClick(View view) {
+                                        mHandler.unblock(onUndoListener, uri);
+                                    }
+                                })
+                        .show();
+                if (mOnBlockListener != null) {
+                    mOnBlockListener.onBlockComplete(uri);
+                }
+            }
+        };
+        mHandler.blockNumber(onBlockNumberListener, getArguments().getString(ARG_NORMALIZED_NUMBER),
                 getArguments().getString(ARG_NUMBER), getArguments().getString(ARG_COUNTRY_ISO));
     }
 
-    public void unblockNumber() {
+    private void unblockNumber() {
         final String displayNumber = getArguments().getString(ARG_DISPLAY_NUMBER);
-        final String message = getString(R.string.snackbar_number_unblocked, displayNumber);
-        final String undoMessage = getString(R.string.snackbar_number_blocked, displayNumber);
-        final FilteredNumberAsyncQueryHandler.OnBlockNumberListener undoListener =
-                new FilteredNumberAsyncQueryHandler.OnBlockNumberListener() {
+        final String message = getUnblockedMessage(displayNumber);
+        final String undoMessage = getBlockedMessage(displayNumber);
+        final OnBlockNumberListener undoListener =
+                new OnBlockNumberListener() {
                     @Override
                     public void onBlockComplete(final Uri uri) {
                         Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show();
                     }
                 };
         mHandler.unblock(
-                new FilteredNumberAsyncQueryHandler.OnUnblockNumberListener() {
+                new OnUnblockNumberListener() {
                     @Override
                     public void onUnblockComplete(int rows, final ContentValues values) {
                         Snackbar.make(mParentView, message, Snackbar.LENGTH_LONG)
diff --git a/src/com/android/dialer/list/BlockedListSearchAdapter.java b/src/com/android/dialer/list/BlockedListSearchAdapter.java
index 52e0c64..7ae24e4 100644
--- a/src/com/android/dialer/list/BlockedListSearchAdapter.java
+++ b/src/com/android/dialer/list/BlockedListSearchAdapter.java
@@ -16,20 +16,74 @@
 package com.android.dialer.list;
 
 import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Color;
+import android.view.View;
+
+import com.android.contacts.common.GeoUtil;
+import com.android.contacts.common.list.ContactListItemView;
+import com.android.dialer.R;
+import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
 
 /**
  * List adapter to display search results for adding a blocked number.
  */
 public class BlockedListSearchAdapter extends RegularSearchListAdapter {
 
+    private Resources mResources;
+    private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler;
+
     public BlockedListSearchAdapter(Context context) {
         super(context);
+        mResources = context.getResources();
         disableAllShortcuts();
         setShortcutEnabled(SHORTCUT_BLOCK_NUMBER, true);
     }
 
+    public void setFilteredNumberAsyncQueryHandler(FilteredNumberAsyncQueryHandler handler) {
+        mFilteredNumberAsyncQueryHandler = handler;
+    }
+
     @Override
     protected boolean isChanged(boolean showNumberShortcuts) {
         return setShortcutEnabled(SHORTCUT_BLOCK_NUMBER, showNumberShortcuts || mIsQuerySipAddress);
     }
+
+    public void setViewBlocked(ContactListItemView view, Integer id) {
+        view.setTag(R.id.block_id, id);
+        final int textColor = mResources.getColor(R.color.blocked_number_block_color);
+        view.getDataView().setTextColor(textColor);
+        view.getLabelView().setTextColor(textColor);
+        //TODO: Add icon
+    }
+
+    public void setViewUnblocked(ContactListItemView view) {
+        view.setTag(R.id.block_id, null);
+        final int textColor = mResources.getColor(R.color.dialtacts_secondary_text_color);
+        view.getDataView().setTextColor(textColor);
+        view.getLabelView().setTextColor(textColor);
+        //TODO: Remove icon
+    }
+
+    @Override
+    protected void bindView(View itemView, int partition, Cursor cursor, int position) {
+        super.bindView(itemView, partition, cursor, position);
+        final ContactListItemView view = (ContactListItemView) itemView;
+        final String number = getPhoneNumber(position);
+        final String countryIso = GeoUtil.getCurrentCountryIso(mContext);
+        final String normalizedNumber =
+                FilteredNumberAsyncQueryHandler.getNormalizedNumber(number, countryIso);
+        final FilteredNumberAsyncQueryHandler.OnCheckBlockedListener onCheckListener =
+                new FilteredNumberAsyncQueryHandler.OnCheckBlockedListener() {
+                    @Override
+                    public void onCheckComplete(Integer id) {
+                        if (id != null) {
+                            setViewBlocked(view, id);
+                        }
+                    }
+                };
+        mFilteredNumberAsyncQueryHandler.startBlockedQuery(
+                onCheckListener, normalizedNumber, number, countryIso);
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/dialer/list/BlockedListSearchFragment.java b/src/com/android/dialer/list/BlockedListSearchFragment.java
index d025772..7494372 100644
--- a/src/com/android/dialer/list/BlockedListSearchFragment.java
+++ b/src/com/android/dialer/list/BlockedListSearchFragment.java
@@ -15,48 +15,138 @@
  */
 package com.android.dialer.list;
 
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.net.Uri;
 import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Toast;
 
+import com.android.contacts.common.GeoUtil;
+import com.android.contacts.common.dialog.IndeterminateProgressDialog;
 import com.android.contacts.common.list.ContactEntryListAdapter;
-import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
+import com.android.contacts.common.list.ContactListItemView;
+import com.android.dialer.R;
+import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
+import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener;
+import com.android.dialer.filterednumber.FilterNumberDialogFragment;
 
 public class BlockedListSearchFragment extends RegularSearchFragment {
     private static final String TAG = BlockedListSearchFragment.class.getSimpleName();
 
+    private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler;
+
+    public void setFilteredNumberAsyncQueryHandler(FilteredNumberAsyncQueryHandler handler) {
+        mFilteredNumberAsyncQueryHandler = handler;
+    }
+
     @Override
     protected ContactEntryListAdapter createListAdapter() {
         BlockedListSearchAdapter adapter = new BlockedListSearchAdapter(getActivity());
         adapter.setDisplayPhotos(true);
         adapter.setUseCallableUri(usesCallableUri());
+        adapter.setFilteredNumberAsyncQueryHandler(mFilteredNumberAsyncQueryHandler);
         return adapter;
     }
 
     @Override
-    protected void onItemClick(int position, long id) {
-        final DialerPhoneNumberListAdapter adapter = (DialerPhoneNumberListAdapter) getAdapter();
-        final int shortcutType = adapter.getShortcutTypeFromPosition(position);
-        final OnPhoneNumberPickerActionListener listener = getOnPhoneNumberPickerListener();
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        super.onItemClick(parent, view, position, id);
+        final int adapterPosition = position - getListView().getHeaderViewsCount();
+        final BlockedListSearchAdapter adapter = (BlockedListSearchAdapter) getAdapter();
+        final int shortcutType = adapter.getShortcutTypeFromPosition(adapterPosition);
+        final Integer blockId = (Integer) view.getTag(R.id.block_id);
         final String number;
-
-        if (listener == null) {
-            Log.d(TAG, "getOnPhoneNumberPickerListener() returned null in onItemClick.");
-            return;
-        }
-
         switch (shortcutType) {
             case DialerPhoneNumberListAdapter.SHORTCUT_INVALID:
                 // Handles click on a search result, either contact or nearby places result.
-                number = adapter.getPhoneNumber(position);
-                listener.onPickPhoneNumber(number, false, getCallInitiationType(false));
+                number = adapter.getPhoneNumber(adapterPosition);
+                blockContactNumber(adapter, (ContactListItemView) view, number, blockId);
                 break;
             case DialerPhoneNumberListAdapter.SHORTCUT_BLOCK_NUMBER:
                 // Handles click on 'Block number' shortcut to add the user query as a number.
                 number = adapter.getQueryString();
-                listener.onPickPhoneNumber(number, false, getCallInitiationType(false));
+                blockNumber(number);
                 break;
             default:
                 Log.w(TAG, "Ignoring unsupported shortcut type: " + shortcutType);
                 break;
         }
     }
+
+    // Prevent SearchFragment.onItemClicked from being called.
+    @Override
+    protected void onItemClick(int position, long id) {
+    }
+
+    private void blockNumber(final String number) {
+        final String countryIso = GeoUtil.getCurrentCountryIso(getContext());
+        final IndeterminateProgressDialog progressDialog =
+                IndeterminateProgressDialog.show(getFragmentManager(),
+                        getString(R.string.checkingNumber, number), null, 500);
+        final String normalizedNumber =
+                FilteredNumberAsyncQueryHandler.getNormalizedNumber(number, countryIso);
+        if (normalizedNumber == null) {
+            progressDialog.dismiss();
+            Toast.makeText(getContext(), getString(R.string.invalidNumber, number),
+                    Toast.LENGTH_SHORT).show();
+            return;
+        }
+        final OnCheckBlockedListener onCheckListener = new OnCheckBlockedListener() {
+            @Override
+            public void onCheckComplete(Integer id) {
+                progressDialog.dismiss();
+                if (id == null) {
+                    final FilterNumberDialogFragment newFragment = FilterNumberDialogFragment
+                            .newInstance(id, normalizedNumber, number, countryIso, number);
+                    newFragment.setQueryHandler(mFilteredNumberAsyncQueryHandler);
+                    newFragment.setParentView(
+                            getActivity().findViewById(R.id.search_activity_container));
+                    newFragment.show(
+                            getFragmentManager(), FilterNumberDialogFragment.BLOCK_DIALOG_FRAGMENT);
+                } else {
+                    Toast.makeText(getContext(), getString(R.string.alreadyBlocked, number),
+                            Toast.LENGTH_SHORT).show();
+                }
+            }
+        };
+        mFilteredNumberAsyncQueryHandler.startBlockedQuery(
+                onCheckListener, normalizedNumber, number, countryIso);
+    }
+
+    private void blockContactNumber(final BlockedListSearchAdapter adapter,
+                                    final ContactListItemView view, final String number,
+                                    final Integer blockId) {
+        final String countryIso = GeoUtil.getCurrentCountryIso(getContext());
+        final String normalizedNumber =
+                FilteredNumberAsyncQueryHandler.getNormalizedNumber(number, countryIso);
+        if (normalizedNumber == null) {
+            Toast.makeText(getContext(), getString(R.string.invalidNumber, number),
+                    Toast.LENGTH_SHORT).show();
+            return;
+        }
+        if (blockId != null) {
+            Toast.makeText(getContext(), getString(R.string.alreadyBlocked, number),
+                    Toast.LENGTH_SHORT).show();
+            return;
+        }
+        final FilterNumberDialogFragment newFragment = FilterNumberDialogFragment
+                .newInstance(blockId, normalizedNumber, number, countryIso, number);
+        newFragment.setQueryHandler(mFilteredNumberAsyncQueryHandler);
+        newFragment.setParentView(getActivity().findViewById(R.id.search_activity_container));
+        newFragment.setOnUndoBlockListener(new FilterNumberDialogFragment.OnUndoBlockListener() {
+            @Override
+            public void onUndoBlockComplete() {
+                adapter.setViewUnblocked(view);
+            }
+        });
+        newFragment.setOnBlockListener(new FilterNumberDialogFragment.OnBlockListener() {
+            @Override
+            public void onBlockComplete(Uri uri) {
+                adapter.setViewBlocked(view, Long.valueOf(ContentUris.parseId(uri)).intValue());
+            }
+        });
+        newFragment.show(getFragmentManager(), FilterNumberDialogFragment.BLOCK_DIALOG_FRAGMENT);
+    }
 }
diff --git a/src/com/android/dialer/onboard/OnboardingActivity.java b/src/com/android/dialer/onboard/OnboardingActivity.java
new file mode 100644
index 0000000..75378e9
--- /dev/null
+++ b/src/com/android/dialer/onboard/OnboardingActivity.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.onboard;
+
+import static android.Manifest.permission.CALL_PHONE;
+import static android.Manifest.permission.READ_CONTACTS;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.telecom.TelecomManager;
+
+import com.android.contacts.common.util.PermissionsUtil;
+import com.android.dialer.TransactionSafeActivity;
+import com.android.dialer.onboard.OnboardingController.OnboardingScreen;
+import com.android.dialer.onboard.OnboardingController.OnboardingUi;
+import com.android.dialer.util.TelecomUtil;
+import com.android.dialer.R;
+
+/**
+ * Activity hosting the onboarding UX flow that appears when you launch Dialer and you don't have
+ * the necessary permissions to run the app.
+ */
+public class OnboardingActivity extends TransactionSafeActivity implements OnboardingUi,
+        PermissionsChecker, OnboardingFragment.HostInterface {
+    public static final String KEY_ALREADY_REQUESTED_DEFAULT_DIALER =
+            "key_already_requested_default_dialer";
+
+    public static final int SCREEN_DEFAULT_DIALER = 0;
+    public static final int SCREEN_PERMISSIONS = 1;
+    public static final int SCREEN_COUNT = 2;
+
+    private OnboardingController mOnboardingController;
+
+    private DefaultDialerOnboardingScreen mDefaultDialerOnboardingScreen;
+    private PermissionsOnboardingScreen mPermissionsOnboardingScreen;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.onboarding_activity);
+        mOnboardingController = new OnboardingController(this);
+        mDefaultDialerOnboardingScreen = new DefaultDialerOnboardingScreen(this);
+        mPermissionsOnboardingScreen = new PermissionsOnboardingScreen(this);
+        mOnboardingController.addScreen(mDefaultDialerOnboardingScreen);
+        mOnboardingController.addScreen(mPermissionsOnboardingScreen);
+
+        mOnboardingController.showNextScreen();
+    }
+
+    @Override
+    public void showScreen(int screenId) {
+        if (!isSafeToCommitTransactions()) {
+            return;
+        }
+        final Fragment fragment;
+        switch (screenId) {
+            case SCREEN_DEFAULT_DIALER:
+                fragment = mDefaultDialerOnboardingScreen.getFragment();
+                break;
+            case SCREEN_PERMISSIONS:
+                fragment = mPermissionsOnboardingScreen.getFragment();
+                break;
+            default:
+                return;
+        }
+
+        final FragmentTransaction ft = getFragmentManager().beginTransaction();
+        ft.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
+        ft.replace(R.id.onboarding_fragment_container, fragment);
+        ft.commit();
+    }
+
+    @Override
+    public void completeOnboardingFlow() {
+        final Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
+        editor.putBoolean(KEY_ALREADY_REQUESTED_DEFAULT_DIALER, true).apply();
+        finish();
+    }
+
+    @Override
+    public boolean hasPhonePermissions() {
+        return PermissionsUtil.hasPhonePermissions(this);
+    }
+
+    @Override
+    public boolean hasContactsPermissions() {
+        return PermissionsUtil.hasContactsPermissions(this);
+    }
+
+    @Override
+    public boolean isDefaultOrSystemDialer() {
+        return TelecomUtil.hasModifyPhoneStatePermission(this);
+    }
+
+    @Override
+    public boolean previouslyRequestedDefaultDialer() {
+        final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+        return preferences.getBoolean(KEY_ALREADY_REQUESTED_DEFAULT_DIALER, false);
+    }
+
+    /**
+     * Triggers the screen-specific logic that should occur when the next button is clicked.
+     */
+    @Override
+    public void onNextClicked(int screenId) {
+        switch (screenId) {
+            case SCREEN_DEFAULT_DIALER:
+                mDefaultDialerOnboardingScreen.onNextClicked(this);
+                break;
+            case SCREEN_PERMISSIONS:
+                mPermissionsOnboardingScreen.onNextClicked(this);
+                break;
+            default:
+                return;
+        }
+    }
+
+    @Override
+    public void onSkipClicked(int screenId) {
+        mOnboardingController.onScreenResult(screenId, false);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == SCREEN_DEFAULT_DIALER
+                && resultCode == RESULT_OK) {
+            mOnboardingController.onScreenResult(SCREEN_DEFAULT_DIALER, true);
+        }
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String[] permissions,
+            int[] grantResults) {
+        boolean allPermissionsGranted = true;
+        if (requestCode == SCREEN_PERMISSIONS) {
+            if (permissions.length == 0 && grantResults.length == 0) {
+                // Cancellation of permissions dialog
+                allPermissionsGranted = false;
+            } else {
+                for (int result : grantResults) {
+                    if (result == PackageManager.PERMISSION_DENIED) {
+                        allPermissionsGranted = false;
+                    }
+                }
+            }
+
+            if (allPermissionsGranted) {
+                mOnboardingController.onScreenResult(SCREEN_PERMISSIONS, true);
+            }
+        }
+    }
+
+    public static class DefaultDialerOnboardingScreen extends OnboardingScreen {
+        private PermissionsChecker mPermissionsChecker;
+
+        public DefaultDialerOnboardingScreen(PermissionsChecker permissionsChecker) {
+            mPermissionsChecker = permissionsChecker;
+        }
+
+        @Override
+        public boolean shouldShowScreen() {
+            return !mPermissionsChecker.previouslyRequestedDefaultDialer()
+                    && !mPermissionsChecker.isDefaultOrSystemDialer();
+        }
+
+        @Override
+        public boolean canSkipScreen() {
+            return true;
+        }
+
+        public Fragment getFragment() {
+            return new OnboardingFragment(
+                    SCREEN_DEFAULT_DIALER,
+                    canSkipScreen(),
+                    R.color.onboarding_default_dialer_screen_background_color,
+                    R.string.request_default_dialer_screen_title,
+                    R.string.request_default_dialer_screen_content
+            );
+        }
+
+        @Override
+        public void onNextClicked(Activity activity) {
+            final Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER);
+            intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME,
+                    activity.getPackageName());
+            activity.startActivityForResult(intent, SCREEN_DEFAULT_DIALER);
+        }
+    }
+
+    public static class PermissionsOnboardingScreen extends OnboardingScreen {
+        private PermissionsChecker mPermissionsChecker;
+
+        public PermissionsOnboardingScreen(PermissionsChecker permissionsChecker) {
+            mPermissionsChecker = permissionsChecker;
+        }
+
+        @Override
+        public boolean shouldShowScreen() {
+            return !(mPermissionsChecker.hasPhonePermissions()
+                    && mPermissionsChecker.hasContactsPermissions());
+        }
+
+        @Override
+        public boolean canSkipScreen() {
+            return false;
+        }
+
+        public Fragment getFragment() {
+            return new OnboardingFragment(
+                    SCREEN_PERMISSIONS,
+                    canSkipScreen(),
+                    R.color.onboarding_permissions_screen_background_color,
+                    R.string.request_permissions_screen_title,
+                    R.string.request_permissions_screen_content
+            );
+        }
+
+        @Override
+        public void onNextClicked(Activity activity) {
+            activity.requestPermissions(new String[] {CALL_PHONE, READ_CONTACTS},
+                    SCREEN_PERMISSIONS);
+        }
+    }
+}
diff --git a/src/com/android/dialer/onboard/OnboardingController.java b/src/com/android/dialer/onboard/OnboardingController.java
new file mode 100644
index 0000000..f799479
--- /dev/null
+++ b/src/com/android/dialer/onboard/OnboardingController.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.onboard;
+
+import android.app.Activity;
+
+import java.util.ArrayList;
+
+/**
+ * Class that manages the display of various fragments that show the user prompts asking for
+ * certain privileged positions.
+ */
+public class OnboardingController {
+    public static abstract class OnboardingScreen {
+        public abstract boolean shouldShowScreen();
+        public abstract boolean canSkipScreen();
+        public abstract void onNextClicked(Activity activity);
+    }
+
+    public interface OnboardingUi {
+        public void showScreen(int screenId);
+        /**
+         * Called when all the necessary permissions have been granted and the main activity
+         * can launch.
+         */
+        public void completeOnboardingFlow();
+    }
+
+    private int mCurrentScreen = -1;
+    private OnboardingUi mOnboardingUi;
+    private ArrayList<OnboardingScreen> mScreens = new ArrayList<> ();
+
+    public OnboardingController(OnboardingUi onBoardingUi) {
+        mOnboardingUi = onBoardingUi;
+    }
+
+    public void addScreen(OnboardingScreen screen) {
+        mScreens.add(screen);
+    }
+
+    public void showNextScreen() {
+        mCurrentScreen++;
+
+        if (mCurrentScreen >= mScreens.size()) {
+            // Reached the end of onboarding flow
+            mOnboardingUi.completeOnboardingFlow();
+            return;
+        }
+
+        if (mScreens.get(mCurrentScreen).shouldShowScreen()) {
+            mOnboardingUi.showScreen(mCurrentScreen);
+        } else {
+            showNextScreen();
+        }
+    }
+
+    public void onScreenResult(int screenId, boolean success) {
+        if (screenId >= mScreens.size()) {
+            return;
+        }
+
+        // Show the next screen in the onboarding flow only under the following situations:
+        // 1) Success was indicated, and the
+        // 2) The user tried to skip the screen, and the screen can be skipped
+        if (success && !mScreens.get(mCurrentScreen).shouldShowScreen()) {
+            showNextScreen();
+        } else if (mScreens.get(mCurrentScreen).canSkipScreen()) {
+            showNextScreen();
+        }
+    }
+}
diff --git a/src/com/android/dialer/onboard/OnboardingFragment.java b/src/com/android/dialer/onboard/OnboardingFragment.java
new file mode 100644
index 0000000..77b265b
--- /dev/null
+++ b/src/com/android/dialer/onboard/OnboardingFragment.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dialer.onboard;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.dialer.R;
+
+public class OnboardingFragment extends Fragment implements OnClickListener {
+    public static final String ARG_SCREEN_ID = "arg_screen_id";
+    public static final String ARG_CAN_SKIP_SCREEN = "arg_can_skip_screen";
+    public static final String ARG_BACKGROUND_COLOR_RESOURCE = "arg_background_color";
+    public static final String ARG_TEXT_TITLE_RESOURCE = "arg_text_title_resource";
+    public static final String ARG_TEXT_CONTENT_RESOURCE = "arg_text_content_resource";
+
+    private int mScreenId;
+
+    public interface HostInterface {
+        public void onNextClicked(int screenId);
+        public void onSkipClicked(int screenId);
+    }
+
+    public OnboardingFragment() {}
+
+    public OnboardingFragment(int screenId, boolean canSkipScreen, int backgroundColorResourceId,
+            int textTitleResourceId, int textContentResourceId) {
+        final Bundle args = new Bundle();
+        args.putInt(ARG_SCREEN_ID, screenId);
+        args.putBoolean(ARG_CAN_SKIP_SCREEN, canSkipScreen);
+        args.putInt(ARG_BACKGROUND_COLOR_RESOURCE, backgroundColorResourceId);
+        args.putInt(ARG_TEXT_TITLE_RESOURCE, textTitleResourceId);
+        args.putInt(ARG_TEXT_CONTENT_RESOURCE, textContentResourceId);
+        setArguments(args);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mScreenId = getArguments().getInt(ARG_SCREEN_ID);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        final View view = inflater.inflate(R.layout.onboarding_screen_fragment, container, false);
+        view.setBackgroundColor(getResources().getColor(
+                getArguments().getInt(ARG_BACKGROUND_COLOR_RESOURCE), null));
+        ((TextView) view.findViewById(R.id.onboarding_screen_content)).
+                setText(getArguments().getInt(ARG_TEXT_CONTENT_RESOURCE));
+        ((TextView) view.findViewById(R.id.onboarding_screen_title)).
+        setText(getArguments().getInt(ARG_TEXT_TITLE_RESOURCE));
+        if (!getArguments().getBoolean(ARG_CAN_SKIP_SCREEN)) {
+            view.findViewById(R.id.onboard_skip_button).setVisibility(View.INVISIBLE);
+        }
+
+        view.findViewById(R.id.onboard_skip_button).setOnClickListener(this);
+        view.findViewById(R.id.onboard_next_button).setOnClickListener(this);
+
+        return view;
+    }
+
+    int getScreenId() {
+        return mScreenId;
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v.getId() == R.id.onboard_skip_button) {
+            ((HostInterface) getActivity()).onSkipClicked(getScreenId());
+        } else if (v.getId() == R.id.onboard_next_button) {
+            ((HostInterface) getActivity()).onNextClicked(getScreenId());
+        }
+    }
+}
diff --git a/src/com/android/dialer/onboard/PermissionsChecker.java b/src/com/android/dialer/onboard/PermissionsChecker.java
new file mode 100644
index 0000000..78d175e
--- /dev/null
+++ b/src/com/android/dialer/onboard/PermissionsChecker.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dialer.onboard;
+
+/**
+ * Defines a mockable interface used to verify whether certain permissions/privileged operations
+ * are possible.
+ */
+public interface PermissionsChecker {
+    public boolean hasPhonePermissions();
+    public boolean hasContactsPermissions();
+    public boolean isDefaultOrSystemDialer();
+    public boolean previouslyRequestedDefaultDialer();
+}
diff --git a/src/com/android/dialer/util/AppCompatConstants.java b/src/com/android/dialer/util/AppCompatConstants.java
new file mode 100644
index 0000000..1d52eee
--- /dev/null
+++ b/src/com/android/dialer/util/AppCompatConstants.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dialer.util;
+
+import android.provider.CallLog.Calls;
+
+public final class AppCompatConstants {
+
+    public static final int CALLS_INCOMING_TYPE = Calls.INCOMING_TYPE;
+    public static final int CALLS_OUTGOING_TYPE = Calls.OUTGOING_TYPE;
+    public static final int CALLS_MISSED_TYPE = Calls.MISSED_TYPE;
+    public static final int CALLS_VOICEMAIL_TYPE = Calls.VOICEMAIL_TYPE;
+    // Added to android.provider.CallLog.Calls in N+.
+    public static final int CALLS_REJECTED_TYPE = 5;
+    // Added to android.provider.CallLog.Calls in N+.
+    public static final int CALLS_BLOCKED_TYPE = 6;
+}
diff --git a/tests/Android.mk b/tests/Android.mk
index 21beca8..07f4f00 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -16,6 +16,9 @@
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs))
 
+LOCAL_STATIC_JAVA_LIBRARIES += \
+        mockito-target
+
 LOCAL_PACKAGE_NAME := DialerTests
 
 LOCAL_INSTRUMENTATION_FOR := Dialer
diff --git a/tests/src/com/android/dialer/CallDetailActivityTest.java b/tests/src/com/android/dialer/CallDetailActivityTest.java
index 59c2434..27fbc30 100644
--- a/tests/src/com/android/dialer/CallDetailActivityTest.java
+++ b/tests/src/com/android/dialer/CallDetailActivityTest.java
@@ -33,6 +33,7 @@
 import android.widget.TextView;
 
 import com.android.dialer.calllog.CallLogAsyncTaskUtil;
+import com.android.dialer.util.AppCompatConstants;
 import com.android.dialer.util.AsyncTaskExecutors;
 import com.android.dialer.util.FakeAsyncTaskExecutor;
 
@@ -121,7 +122,7 @@
         ContentValues values = new ContentValues();
         values.put(CallLog.Calls.NUMBER, CONTACT_NUMBER);
         values.put(CallLog.Calls.NUMBER_PRESENTATION, CallLog.Calls.PRESENTATION_ALLOWED);
-        values.put(CallLog.Calls.TYPE, CallLog.Calls.INCOMING_TYPE);
+        values.put(CallLog.Calls.TYPE, AppCompatConstants.CALLS_INCOMING_TYPE);
         mCallLogUri = contentResolver.insert(CallLog.Calls.CONTENT_URI, values);
         setActivityIntent(new Intent(Intent.ACTION_VIEW, mCallLogUri));
     }
diff --git a/tests/src/com/android/dialer/calllog/CallLogAdapterTest.java b/tests/src/com/android/dialer/calllog/CallLogAdapterTest.java
index 83d098f..3a79695 100644
--- a/tests/src/com/android/dialer/calllog/CallLogAdapterTest.java
+++ b/tests/src/com/android/dialer/calllog/CallLogAdapterTest.java
@@ -35,6 +35,7 @@
 
 import com.android.dialer.contactinfo.ContactInfoCache;
 import com.android.dialer.contactinfo.ContactInfoCache.OnContactInfoChangedListener;
+import com.android.dialer.util.AppCompatConstants;
 import com.android.dialer.util.TestConstants;
 import com.google.common.collect.Lists;
 
@@ -344,7 +345,7 @@
     public void testBindView_UriNumber() {
         createCallLogEntryWithCachedValues(
                 "sip:johndoe@gmail.com",
-                Calls.INCOMING_TYPE,
+                AppCompatConstants.CALLS_INCOMING_TYPE,
                 "John Doe",
                 Phone.TYPE_HOME,
                 TEST_DEFAULT_CUSTOM_LABEL,
@@ -404,7 +405,7 @@
     public void testBindView_NumberOnlyDbCachedFormattedNumber() {
         createCallLogEntryWithCachedValues(
                 TEST_NUMBER,
-                Calls.INCOMING_TYPE,
+                AppCompatConstants.CALLS_INCOMING_TYPE,
                 EMPTY_STRING,
                 TEST_CACHED_NUMBER_TYPE,
                 TEST_CACHED_NUMBER_LABEL,
@@ -454,11 +455,17 @@
     }
 
     private void createPrivateCallLogEntry() {
-        createCallLogEntry(EMPTY_STRING, Calls.PRESENTATION_RESTRICTED, Calls.INCOMING_TYPE);
+        createCallLogEntry(
+                EMPTY_STRING,
+                Calls.PRESENTATION_RESTRICTED,
+                AppCompatConstants.CALLS_INCOMING_TYPE);
     }
 
     private void createUnknownCallLogEntry() {
-        createCallLogEntry(EMPTY_STRING, Calls.PRESENTATION_UNKNOWN, Calls.INCOMING_TYPE);
+        createCallLogEntry(
+                EMPTY_STRING,
+                Calls.PRESENTATION_UNKNOWN,
+                AppCompatConstants.CALLS_INCOMING_TYPE);
     }
 
     private void createVoicemailCallLogEntry() {
@@ -499,7 +506,7 @@
      * It includes the values for the cached contact associated with the number.
      *
      * @param number The phone number.
-     * @param type Either Call.OUTGOING_TYPE or Call.INCOMING_TYPE or Call.MISSED_TYPE.
+     * @param type Valid value of {@code Calls.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.
@@ -555,7 +562,7 @@
         if (type != NO_VALUE_SET) {
             values[CallLogQuery.CALL_TYPE] = type;
         }
-        if (type == Calls.VOICEMAIL_TYPE) {
+        if (type == AppCompatConstants.CALLS_VOICEMAIL_TYPE) {
             values[CallLogQuery.VOICEMAIL_URI] = ContentUris.withAppendedId(
                     VoicemailContract.Voicemails.CONTENT_URI, mCursor.getCount());
         }
diff --git a/tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java b/tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java
index 9bdcd40..f5a9fe6 100644
--- a/tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java
+++ b/tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java
@@ -19,10 +19,11 @@
 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 com.android.dialer.util.AppCompatConstants;
+
 import java.util.List;
 
 /**
@@ -64,31 +65,31 @@
     }
 
     public void testAddGroups_OneCall() {
-        addCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
+        addCallLogEntry(TEST_NUMBER1, AppCompatConstants.CALLS_INCOMING_TYPE);
         mBuilder.addGroups(mCursor);
         assertEquals(1, mFakeGroupCreator.groups.size());
     }
 
     public void testAddGroups_TwoCallsNotMatching() {
-        addCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
-        addCallLogEntry(TEST_NUMBER2, Calls.INCOMING_TYPE);
+        addCallLogEntry(TEST_NUMBER1, AppCompatConstants.CALLS_INCOMING_TYPE);
+        addCallLogEntry(TEST_NUMBER2, AppCompatConstants.CALLS_INCOMING_TYPE);
         mBuilder.addGroups(mCursor);
         assertEquals(2, mFakeGroupCreator.groups.size());
     }
 
     public void testAddGroups_ThreeCallsMatching() {
-        addCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
-        addCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
-        addCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
+        addCallLogEntry(TEST_NUMBER1, AppCompatConstants.CALLS_INCOMING_TYPE);
+        addCallLogEntry(TEST_NUMBER1, AppCompatConstants.CALLS_INCOMING_TYPE);
+        addCallLogEntry(TEST_NUMBER1, AppCompatConstants.CALLS_INCOMING_TYPE);
         mBuilder.addGroups(mCursor);
         assertEquals(1, mFakeGroupCreator.groups.size());
         assertGroupIs(0, 3, mFakeGroupCreator.groups.get(0));
     }
 
     public void testAddGroups_MatchingIncomingAndOutgoing() {
-        addCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
-        addCallLogEntry(TEST_NUMBER1, Calls.OUTGOING_TYPE);
-        addCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
+        addCallLogEntry(TEST_NUMBER1, AppCompatConstants.CALLS_INCOMING_TYPE);
+        addCallLogEntry(TEST_NUMBER1, AppCompatConstants.CALLS_OUTGOING_TYPE);
+        addCallLogEntry(TEST_NUMBER1, AppCompatConstants.CALLS_INCOMING_TYPE);
         mBuilder.addGroups(mCursor);
         assertEquals(1, mFakeGroupCreator.groups.size());
         assertGroupIs(0, 3, mFakeGroupCreator.groups.get(0));
@@ -96,57 +97,88 @@
 
     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.VOICEMAIL_TYPE);
-        assertCallsAreNotGrouped(Calls.VOICEMAIL_TYPE, Calls.INCOMING_TYPE);
-        assertCallsAreNotGrouped(Calls.VOICEMAIL_TYPE, Calls.OUTGOING_TYPE);
+        assertCallsAreNotGrouped(
+                AppCompatConstants.CALLS_VOICEMAIL_TYPE, AppCompatConstants.CALLS_MISSED_TYPE);
+        assertCallsAreNotGrouped(
+                AppCompatConstants.CALLS_VOICEMAIL_TYPE, AppCompatConstants.CALLS_VOICEMAIL_TYPE);
+        assertCallsAreNotGrouped(
+                AppCompatConstants.CALLS_VOICEMAIL_TYPE, AppCompatConstants.CALLS_INCOMING_TYPE);
+        assertCallsAreNotGrouped(
+                AppCompatConstants.CALLS_VOICEMAIL_TYPE, AppCompatConstants.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);
+        assertCallsAreGrouped(
+                AppCompatConstants.CALLS_MISSED_TYPE, AppCompatConstants.CALLS_MISSED_TYPE);
+        assertCallsAreGrouped(
+                AppCompatConstants.CALLS_MISSED_TYPE,
+                AppCompatConstants.CALLS_MISSED_TYPE,
+                AppCompatConstants.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);
+        assertCallsAreNotGrouped(
+                AppCompatConstants.CALLS_MISSED_TYPE, AppCompatConstants.CALLS_VOICEMAIL_TYPE);
+        assertCallsAreGrouped(
+                AppCompatConstants.CALLS_MISSED_TYPE, AppCompatConstants.CALLS_INCOMING_TYPE);
+        assertCallsAreGrouped(
+                AppCompatConstants.CALLS_MISSED_TYPE, AppCompatConstants.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);
+        assertCallsAreGrouped(
+                AppCompatConstants.CALLS_INCOMING_TYPE, AppCompatConstants.CALLS_INCOMING_TYPE);
+        assertCallsAreGrouped(
+                AppCompatConstants.CALLS_INCOMING_TYPE, AppCompatConstants.CALLS_OUTGOING_TYPE);
+        assertCallsAreGrouped(
+                AppCompatConstants.CALLS_INCOMING_TYPE,
+                AppCompatConstants.CALLS_INCOMING_TYPE,
+                AppCompatConstants.CALLS_OUTGOING_TYPE);
+        assertCallsAreGrouped(
+                AppCompatConstants.CALLS_INCOMING_TYPE,
+                AppCompatConstants.CALLS_OUTGOING_TYPE,
+                AppCompatConstants.CALLS_INCOMING_TYPE);
+        assertCallsAreGrouped(
+                AppCompatConstants.CALLS_INCOMING_TYPE, AppCompatConstants.CALLS_MISSED_TYPE);
         // Does not group with voicemail and missed calls.
-        assertCallsAreNotGrouped(Calls.INCOMING_TYPE, Calls.VOICEMAIL_TYPE);
+        assertCallsAreNotGrouped(
+                AppCompatConstants.CALLS_INCOMING_TYPE, AppCompatConstants.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);
+        assertCallsAreGrouped(
+                AppCompatConstants.CALLS_OUTGOING_TYPE, AppCompatConstants.CALLS_INCOMING_TYPE);
+        assertCallsAreGrouped(
+                AppCompatConstants.CALLS_OUTGOING_TYPE, AppCompatConstants.CALLS_OUTGOING_TYPE);
+        assertCallsAreGrouped(
+                AppCompatConstants.CALLS_OUTGOING_TYPE,
+                AppCompatConstants.CALLS_INCOMING_TYPE,
+                AppCompatConstants.CALLS_OUTGOING_TYPE);
+        assertCallsAreGrouped(
+                AppCompatConstants.CALLS_OUTGOING_TYPE,
+                AppCompatConstants.CALLS_OUTGOING_TYPE,
+                AppCompatConstants.CALLS_INCOMING_TYPE);
+        assertCallsAreGrouped(
+                AppCompatConstants.CALLS_INCOMING_TYPE, AppCompatConstants.CALLS_MISSED_TYPE);
         // Does not group with voicemail and missed calls.
-        assertCallsAreNotGrouped(Calls.INCOMING_TYPE, Calls.VOICEMAIL_TYPE);
+        assertCallsAreNotGrouped(
+                AppCompatConstants.CALLS_INCOMING_TYPE, AppCompatConstants.CALLS_VOICEMAIL_TYPE);
     }
 
     public void testAddGroups_Mixed() {
         addMultipleCallLogEntries(TEST_NUMBER1,
-                Calls.VOICEMAIL_TYPE,   // Group 1:Stand-alone
-                Calls.INCOMING_TYPE,    // Group 2: 1-4
-                Calls.OUTGOING_TYPE,
-                Calls.MISSED_TYPE,
-                Calls.MISSED_TYPE,
-                Calls.VOICEMAIL_TYPE,   // Group 3: Stand-alone
-                Calls.INCOMING_TYPE,    // Group 4: Stand-alone
-                Calls.VOICEMAIL_TYPE,   // Group 5: Stand-alone
-                Calls.MISSED_TYPE,      // Group 6: 8-10
-                Calls.MISSED_TYPE,
-                Calls.OUTGOING_TYPE);
+                AppCompatConstants.CALLS_VOICEMAIL_TYPE,   // Group 1:Stand-alone
+                AppCompatConstants.CALLS_INCOMING_TYPE,    // Group 2: 1-4
+                AppCompatConstants.CALLS_OUTGOING_TYPE,
+                AppCompatConstants.CALLS_MISSED_TYPE,
+                AppCompatConstants.CALLS_MISSED_TYPE,
+                AppCompatConstants.CALLS_VOICEMAIL_TYPE,   // Group 3: Stand-alone
+                AppCompatConstants.CALLS_INCOMING_TYPE,    // Group 4: Stand-alone
+                AppCompatConstants.CALLS_VOICEMAIL_TYPE,   // Group 5: Stand-alone
+                AppCompatConstants.CALLS_MISSED_TYPE,      // Group 6: 8-10
+                AppCompatConstants.CALLS_MISSED_TYPE,
+                AppCompatConstants.CALLS_OUTGOING_TYPE);
         mBuilder.addGroups(mCursor);
         assertEquals(6, mFakeGroupCreator.groups.size());
         assertGroupIs(0, 1, mFakeGroupCreator.groups.get(0));
diff --git a/tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java b/tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java
index 8c2d8e4..d554ad4 100644
--- a/tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java
+++ b/tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java
@@ -25,6 +25,7 @@
 import com.android.contacts.common.CallUtil;
 import com.android.dialer.PhoneCallDetails;
 import com.android.dialer.R;
+import com.android.dialer.util.AppCompatConstants;
 
 /**
  * Unit tests for {@link CallLogListItemHelper}.
@@ -103,20 +104,22 @@
     }
 
     public void testSetPhoneCallDetails_ReadVoicemail() {
-        PhoneCallDetails details = getPhoneCallDetailsWithTypes(Calls.VOICEMAIL_TYPE);
+        PhoneCallDetails details =
+                getPhoneCallDetailsWithTypes(AppCompatConstants.CALLS_VOICEMAIL_TYPE);
         mHelper.setPhoneCallDetails(mViewHolder, details);
         assertEquals(View.VISIBLE, mViewHolder.voicemailPlaybackView.getVisibility());
     }
 
     public void testSetPhoneCallDetails_UnreadVoicemail() {
-        PhoneCallDetails details = getPhoneCallDetailsWithTypes(Calls.VOICEMAIL_TYPE);
+        PhoneCallDetails details =
+                getPhoneCallDetailsWithTypes(AppCompatConstants.CALLS_VOICEMAIL_TYPE);
         mHelper.setPhoneCallDetails(mViewHolder, details);
         assertEquals(View.VISIBLE, mViewHolder.voicemailPlaybackView.getVisibility());
     }
 
     public void testSetPhoneCallDetails_VoicemailFromUnknown() {
         setPhoneCallDetailsWithNumberAndType("", Calls.PRESENTATION_UNKNOWN,
-                "", Calls.VOICEMAIL_TYPE);
+                "", AppCompatConstants.CALLS_VOICEMAIL_TYPE);
         assertEquals(View.VISIBLE, mViewHolder.voicemailPlaybackView.getVisibility());
     }
 
@@ -124,7 +127,7 @@
      * Test getCallDescriptionID method used to get the accessibility description for calls.
      */
     public void testGetCallDescriptionID_Answered() {
-        int[] callTypes = new int[]{ Calls.INCOMING_TYPE };
+        int[] callTypes = new int[]{ AppCompatConstants.CALLS_INCOMING_TYPE };
         assertEquals(R.string.description_incoming_answered_call,
                 mHelper.getCallDescriptionStringID(callTypes));
     }
@@ -133,7 +136,7 @@
      * Test getCallDescriptionID method used to get the accessibility description for calls.
      */
     public void testGetCallDescriptionID_Missed() {
-        int[] callTypes = new int[]{ Calls.MISSED_TYPE };
+        int[] callTypes = new int[]{ AppCompatConstants.CALLS_MISSED_TYPE };
         assertEquals(R.string.description_incoming_missed_call,
                 mHelper.getCallDescriptionStringID(callTypes));
     }
@@ -142,7 +145,7 @@
      * Test getCallDescriptionID method used to get the accessibility description for calls.
      */
     public void testGetCallDescriptionID_Voicemail() {
-        int[] callTypes = new int[]{ Calls.VOICEMAIL_TYPE };
+        int[] callTypes = new int[]{ AppCompatConstants.CALLS_VOICEMAIL_TYPE };
         assertEquals(R.string.description_incoming_missed_call,
                 mHelper.getCallDescriptionStringID(callTypes));
     }
@@ -153,7 +156,7 @@
      * only a single call for this caller.
      */
     public void testGetCallDescriptionID_OutgoingSingle() {
-        int[] callTypes = new int[]{ Calls.OUTGOING_TYPE };
+        int[] callTypes = new int[]{ AppCompatConstants.CALLS_OUTGOING_TYPE };
         assertEquals(R.string.description_outgoing_call,
                 mHelper.getCallDescriptionStringID(callTypes));
     }
@@ -164,7 +167,10 @@
      * many calls for this caller.
      */
     public void testGetCallDescriptionID_OutgoingMultiple() {
-        int[] callTypes = new int[]{ Calls.OUTGOING_TYPE, Calls.OUTGOING_TYPE };
+        int[] callTypes = new int[]{
+            AppCompatConstants.CALLS_OUTGOING_TYPE,
+            AppCompatConstants.CALLS_OUTGOING_TYPE
+        };
         assertEquals(R.string.description_outgoing_call,
                 mHelper.getCallDescriptionStringID(callTypes));
     }
@@ -174,8 +180,8 @@
      * For outgoing calls, we should NOT have "New Voicemail" in the description.
      */
     public void testGetCallDescription_NoVoicemailOutgoing() {
-        PhoneCallDetails details =
-                getPhoneCallDetailsWithTypes(Calls.OUTGOING_TYPE, Calls.OUTGOING_TYPE);
+        PhoneCallDetails details = getPhoneCallDetailsWithTypes(
+                AppCompatConstants.CALLS_OUTGOING_TYPE, AppCompatConstants.CALLS_OUTGOING_TYPE);
         CharSequence description = mHelper.getCallDescription(details);
         assertFalse(description.toString()
                 .contains(this.mResources.getString(R.string.description_new_voicemail)));
@@ -186,8 +192,8 @@
      * For regular incoming calls, we should NOT have "New Voicemail" in the description.
      */
     public void testGetCallDescription_NoVoicemailIncoming() {
-        PhoneCallDetails details =
-                getPhoneCallDetailsWithTypes(Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
+        PhoneCallDetails details = getPhoneCallDetailsWithTypes(
+                AppCompatConstants.CALLS_INCOMING_TYPE, AppCompatConstants.CALLS_OUTGOING_TYPE);
         CharSequence description = mHelper.getCallDescription(details);
         assertFalse(description.toString()
                 .contains(this.mResources.getString(R.string.description_new_voicemail)));
@@ -198,8 +204,8 @@
      * For regular missed calls, we should NOT have "New Voicemail" in the description.
      */
     public void testGetCallDescription_NoVoicemailMissed() {
-        PhoneCallDetails details =
-                getPhoneCallDetailsWithTypes(Calls.MISSED_TYPE, Calls.OUTGOING_TYPE);
+        PhoneCallDetails details = getPhoneCallDetailsWithTypes(
+                AppCompatConstants.CALLS_MISSED_TYPE, AppCompatConstants.CALLS_OUTGOING_TYPE);
         CharSequence description = mHelper.getCallDescription(details);
         assertFalse(description.toString()
                 .contains(this.mResources.getString(R.string.description_new_voicemail)));
@@ -210,8 +216,8 @@
      * For voicemail calls, we should have "New Voicemail" in the description.
      */
     public void testGetCallDescription_Voicemail() {
-        PhoneCallDetails details =
-                getPhoneCallDetailsWithTypes(Calls.VOICEMAIL_TYPE, Calls.OUTGOING_TYPE);
+        PhoneCallDetails details = getPhoneCallDetailsWithTypes(
+                AppCompatConstants.CALLS_VOICEMAIL_TYPE, AppCompatConstants.CALLS_OUTGOING_TYPE);
         CharSequence description = mHelper.getCallDescription(details);
         assertTrue(description.toString()
                 .contains(this.mResources.getString(R.string.description_new_voicemail)));
@@ -222,7 +228,8 @@
      * Test that the "X calls" message is not present if there is only a single call.
      */
     public void testGetCallDescription_NumCallsSingle() {
-        PhoneCallDetails details = getPhoneCallDetailsWithTypes(Calls.VOICEMAIL_TYPE);
+        PhoneCallDetails details =
+                getPhoneCallDetailsWithTypes(AppCompatConstants.CALLS_VOICEMAIL_TYPE);
         CharSequence description = mHelper.getCallDescription(details);
 
         // Rather than hard coding the "X calls" string message, we'll generate it with an empty
@@ -238,8 +245,8 @@
      * Test that the "X calls" message is present if there are many calls.
      */
     public void testGetCallDescription_NumCallsMultiple() {
-        PhoneCallDetails details =
-                getPhoneCallDetailsWithTypes(Calls.VOICEMAIL_TYPE, Calls.INCOMING_TYPE);
+        PhoneCallDetails details = getPhoneCallDetailsWithTypes(
+                AppCompatConstants.CALLS_VOICEMAIL_TYPE, AppCompatConstants.CALLS_INCOMING_TYPE);
         CharSequence description = mHelper.getCallDescription(details);
         assertTrue(description.toString()
                 .contains(this.mResources.getString(R.string.description_num_calls, 2)));
@@ -250,8 +257,8 @@
      * Test that the "Video call." message is present if the call had video capability.
      */
     public void testGetCallDescription_Video() {
-        PhoneCallDetails details =
-                getPhoneCallDetailsWithTypes(Calls.INCOMING_TYPE, Calls.INCOMING_TYPE);
+        PhoneCallDetails details = getPhoneCallDetailsWithTypes(
+                AppCompatConstants.CALLS_INCOMING_TYPE, AppCompatConstants.CALLS_INCOMING_TYPE);
         details.features = Calls.FEATURES_VIDEO;
 
         CharSequence description = mHelper.getCallDescription(details);
diff --git a/tests/src/com/android/dialer/calllog/PhoneCallDetailsHelperTest.java b/tests/src/com/android/dialer/calllog/PhoneCallDetailsHelperTest.java
index 3182502..263642c 100644
--- a/tests/src/com/android/dialer/calllog/PhoneCallDetailsHelperTest.java
+++ b/tests/src/com/android/dialer/calllog/PhoneCallDetailsHelperTest.java
@@ -27,6 +27,7 @@
 
 import com.android.dialer.PhoneCallDetails;
 import com.android.dialer.R;
+import com.android.dialer.util.AppCompatConstants;
 import com.android.dialer.util.LocaleTestUtils;
 
 import java.util.GregorianCalendar;
@@ -41,6 +42,8 @@
     /** The date of the call log entry. */
     private static final long TEST_DATE =
         new GregorianCalendar(2011, 5, 3, 13, 0, 0).getTimeInMillis();
+    private static final long INJECTED_CURRENT_DATE =
+        new GregorianCalendar(2011, 5, 4, 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. */
@@ -73,8 +76,7 @@
         final TestTelecomCallLogCache phoneUtils = new TestTelecomCallLogCache(
                 mContext, TEST_VOICEMAIL_NUMBER);
         mHelper = new PhoneCallDetailsHelper(mContext, resources, phoneUtils);
-        mHelper.setCurrentTimeForTest(
-                new GregorianCalendar(2011, 5, 4, 13, 0, 0).getTimeInMillis());
+        mHelper.setCurrentTimeForTest(INJECTED_CURRENT_DATE);
         mViews = PhoneCallDetailsViews.createForTest(mContext);
         mNameView = new TextView(mContext);
         mLocaleTestUtils = new LocaleTestUtils(mContext);
@@ -111,10 +113,25 @@
         assertNameEqualsResource(R.string.voicemail);
     }
 
-    public void testSetPhoneCallDetails_Normal() {
-        setPhoneCallDetailsWithNumber("14125551212",
-                Calls.PRESENTATION_ALLOWED, "1-412-555-1212");
-        assertTrue(mViews.callLocationAndDate.getText().toString().contains("Yesterday"));
+    // Voicemail date string has 3 different formats depending on how long ago the call was placed
+    public void testSetVoicemailPhoneCallDetails_Today() {
+        setVoicemailPhoneCallDetailsWithDate(System.currentTimeMillis());
+        assertDateEquals("Today at");
+    }
+
+    public void testSetVoicemailPhoneCallDetails_WithinCurrentYear() {
+        mHelper.setCurrentTimeForTest(INJECTED_CURRENT_DATE);
+        String formattedTestDate = "Jun 3 at 1:00 PM";
+        setVoicemailPhoneCallDetailsWithDate(TEST_DATE);
+        assertDateEquals(formattedTestDate);
+    }
+
+    public void testSetVoicemailPhoneCallDetails_OutsideCurrentYear() {
+        mHelper.setCurrentTimeForTest(INJECTED_CURRENT_DATE);
+        long testDate = new GregorianCalendar(2009, 5, 3, 13, 0, 0).getTimeInMillis();
+        String formattedTestDate = "Jun 3, 2009 at 1:00 PM";
+        setVoicemailPhoneCallDetailsWithDate(testDate);
+        assertDateEquals(formattedTestDate);
     }
 
     /** Asserts that a char sequence is actually a Spanned corresponding to the expected HTML. */
@@ -149,17 +166,17 @@
     }
 
     public void testSetPhoneCallDetails_CallTypeIcons() {
-        setPhoneCallDetailsWithCallTypeIcons(Calls.INCOMING_TYPE);
-        assertCallTypeIconsEquals(Calls.INCOMING_TYPE);
+        setPhoneCallDetailsWithCallTypeIcons(AppCompatConstants.CALLS_INCOMING_TYPE);
+        assertCallTypeIconsEquals(AppCompatConstants.CALLS_INCOMING_TYPE);
 
-        setPhoneCallDetailsWithCallTypeIcons(Calls.OUTGOING_TYPE);
-        assertCallTypeIconsEquals(Calls.OUTGOING_TYPE);
+        setPhoneCallDetailsWithCallTypeIcons(AppCompatConstants.CALLS_OUTGOING_TYPE);
+        assertCallTypeIconsEquals(AppCompatConstants.CALLS_OUTGOING_TYPE);
 
-        setPhoneCallDetailsWithCallTypeIcons(Calls.MISSED_TYPE);
-        assertCallTypeIconsEquals(Calls.MISSED_TYPE);
+        setPhoneCallDetailsWithCallTypeIcons(AppCompatConstants.CALLS_MISSED_TYPE);
+        assertCallTypeIconsEquals(AppCompatConstants.CALLS_MISSED_TYPE);
 
-        setPhoneCallDetailsWithCallTypeIcons(Calls.VOICEMAIL_TYPE);
-        assertCallTypeIconsEquals(Calls.VOICEMAIL_TYPE);
+        setPhoneCallDetailsWithCallTypeIcons(AppCompatConstants.CALLS_VOICEMAIL_TYPE);
+        assertCallTypeIconsEquals(AppCompatConstants.CALLS_VOICEMAIL_TYPE);
     }
 
     /**
@@ -185,18 +202,31 @@
     }
 
     public void testSetPhoneCallDetails_MultipleCallTypeIcons() {
-        setPhoneCallDetailsWithCallTypeIcons(Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
-        assertCallTypeIconsEquals(Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
+        setPhoneCallDetailsWithCallTypeIcons(
+                AppCompatConstants.CALLS_INCOMING_TYPE,
+                AppCompatConstants.CALLS_OUTGOING_TYPE);
+        assertCallTypeIconsEquals(
+                AppCompatConstants.CALLS_INCOMING_TYPE,
+                AppCompatConstants.CALLS_OUTGOING_TYPE);
 
-        setPhoneCallDetailsWithCallTypeIcons(Calls.MISSED_TYPE, Calls.MISSED_TYPE);
-        assertCallTypeIconsEquals(Calls.MISSED_TYPE, Calls.MISSED_TYPE);
+        setPhoneCallDetailsWithCallTypeIcons(
+                AppCompatConstants.CALLS_MISSED_TYPE,
+                AppCompatConstants.CALLS_MISSED_TYPE);
+        assertCallTypeIconsEquals(
+                AppCompatConstants.CALLS_MISSED_TYPE,
+                AppCompatConstants.CALLS_MISSED_TYPE);
     }
 
     public void testSetPhoneCallDetails_MultipleCallTypeIconsLastOneDropped() {
-        setPhoneCallDetailsWithCallTypeIcons(Calls.MISSED_TYPE, Calls.MISSED_TYPE,
-                Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
+        setPhoneCallDetailsWithCallTypeIcons(
+                AppCompatConstants.CALLS_MISSED_TYPE,
+                AppCompatConstants.CALLS_MISSED_TYPE,
+                AppCompatConstants.CALLS_INCOMING_TYPE,
+                AppCompatConstants.CALLS_OUTGOING_TYPE);
         assertCallTypeIconsEqualsPlusOverflow("(4)",
-                Calls.MISSED_TYPE, Calls.MISSED_TYPE, Calls.INCOMING_TYPE);
+                AppCompatConstants.CALLS_MISSED_TYPE,
+                AppCompatConstants.CALLS_MISSED_TYPE,
+                AppCompatConstants.CALLS_INCOMING_TYPE);
     }
 
     public void testSetPhoneCallDetails_Geocode() {
@@ -300,7 +330,6 @@
             assertEquals(id, mViews.callTypeIcons.getCallType(index));
         }
         assertEquals(View.VISIBLE, mViews.callTypeIcons.getVisibility());
-        assertTrue(mViews.callLocationAndDate.getText().toString().contains("Yesterday"));
     }
 
     /**
@@ -322,7 +351,7 @@
     private void setPhoneCallDetailsWithNumber(String number, int presentation,
             String formattedNumber) {
         PhoneCallDetails details = getPhoneCallDetails(number, presentation, formattedNumber);
-        details.callTypes = new int[]{ Calls.VOICEMAIL_TYPE };
+        details.callTypes = new int[]{ AppCompatConstants.CALLS_VOICEMAIL_TYPE };
         mHelper.setPhoneCallDetails(mViews, details);
     }
 
@@ -342,6 +371,13 @@
         mHelper.setPhoneCallDetails(mViews, details);
     }
 
+    private void setVoicemailPhoneCallDetailsWithDate(long date) {
+        PhoneCallDetails details = getPhoneCallDetails();
+        details.date = date;
+        details.callTypes = new int[] {Calls.VOICEMAIL_TYPE};
+        mHelper.setPhoneCallDetails(mViews, details);
+    }
+
     /** Sets the phone call details with default values and the given call types using icons. */
     private void setPhoneCallDetailsWithCallTypeIcons(int... callTypes) {
         PhoneCallDetails details = getPhoneCallDetails();
@@ -384,7 +420,7 @@
     }
 
     private void setDefaultDetails(PhoneCallDetails details) {
-        details.callTypes = new int[]{ Calls.INCOMING_TYPE };
+        details.callTypes = new int[]{ AppCompatConstants.CALLS_INCOMING_TYPE };
         details.countryIso = TEST_COUNTRY_ISO;
         details.date = TEST_DATE;
         details.duration = TEST_DURATION;
diff --git a/tests/src/com/android/dialer/onboard/DefaultDialerOnboardScreenTest.java b/tests/src/com/android/dialer/onboard/DefaultDialerOnboardScreenTest.java
new file mode 100644
index 0000000..f9724c1
--- /dev/null
+++ b/tests/src/com/android/dialer/onboard/DefaultDialerOnboardScreenTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dialer.onboard;
+
+import static org.mockito.Mockito.when;
+
+import android.test.AndroidTestCase;
+
+import com.android.dialer.onboard.OnboardingActivity.DefaultDialerOnboardingScreen;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class DefaultDialerOnboardScreenTest extends AndroidTestCase {
+    private DefaultDialerOnboardingScreen mScreen;
+    @Mock private PermissionsChecker mPermissionsChecker;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        mScreen = new DefaultDialerOnboardingScreen(mPermissionsChecker);
+    }
+
+    public void testNeverRequestedForDefaultDialer_shouldShowScreen() {
+        when(mPermissionsChecker.previouslyRequestedDefaultDialer()).thenReturn(false);
+        assertTrue(mScreen.shouldShowScreen());
+    }
+
+    public void testAlreadyAskedForDefaultDialer_shouldNotShowScreen() {
+        when(mPermissionsChecker.previouslyRequestedDefaultDialer()).thenReturn(true);
+        assertFalse(mScreen.shouldShowScreen());
+    }
+
+    public void testAlreadySetAsDefaultDialer_shouldNotShowScreen() {
+        when(mPermissionsChecker.previouslyRequestedDefaultDialer()).thenReturn(false);
+        when(mPermissionsChecker.isDefaultOrSystemDialer()).thenReturn(true);
+        assertFalse(mScreen.shouldShowScreen());
+    }
+
+    public void testCanSkipScreen() {
+        assertTrue(mScreen.canSkipScreen());
+    }
+}
diff --git a/tests/src/com/android/dialer/onboard/OnboardingControllerTest.java b/tests/src/com/android/dialer/onboard/OnboardingControllerTest.java
new file mode 100644
index 0000000..3ca39a7
--- /dev/null
+++ b/tests/src/com/android/dialer/onboard/OnboardingControllerTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dialer.onboard;
+
+import android.app.Activity;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.Suppress;
+
+public class OnboardingControllerTest extends AndroidTestCase {
+    private MockOnboardUi mOnboardUi;
+    private OnboardingController mController;
+
+    public class MockOnboardUi implements OnboardingController.OnboardingUi {
+        public int currentScreen = -1;
+        public boolean completedOnboardingFlow = false;
+
+        @Override
+        public void showScreen(int screenId) {
+            currentScreen = screenId;
+        }
+
+        @Override
+        public void completeOnboardingFlow() {
+            completedOnboardingFlow = true;
+        }
+    }
+
+    public class MockScreen extends OnboardingController.OnboardingScreen {
+        boolean shouldShowScreen;
+        boolean canSkipScreen;
+
+        public MockScreen(boolean shouldShowScreen, boolean canSkipScreen) {
+            this.shouldShowScreen = shouldShowScreen;
+            this.canSkipScreen = canSkipScreen;
+        }
+
+        @Override
+        public boolean shouldShowScreen() {
+            return shouldShowScreen;
+        }
+
+        @Override
+        public boolean canSkipScreen() {
+            return canSkipScreen;
+        }
+
+        @Override
+        public void onNextClicked(Activity activity) {
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mOnboardUi = new MockOnboardUi();
+        mController = new OnboardingController(mOnboardUi);
+    }
+
+    @Suppress
+    public void testNoScreensToDisplay_OnboardingFlowImmediatelyCompleted() {
+        mController.showNextScreen();
+        assertEquals(-1, mOnboardUi.currentScreen);
+        assertTrue(mOnboardUi.completedOnboardingFlow);
+    }
+
+    @Suppress
+    public void testSkipAllScreens_OnboardingFlowImmediatelyCompleted() {
+        mController.addScreen(new MockScreen(false /* shouldShowScreen */,
+                true /* canSkipScreen */));
+        mController.addScreen(new MockScreen(false /* shouldShowScreen */,
+                true /* canSkipScreen */));
+        mController.addScreen(new MockScreen(false /* shouldShowScreen */,
+                true /* canSkipScreen */));
+        mController.showNextScreen();
+        assertEquals(-1, mOnboardUi.currentScreen);
+        assertTrue(mOnboardUi.completedOnboardingFlow);
+    }
+
+    @Suppress
+    public void testFirstScreenNotNeeded_ShowsSecondScreen() {
+        mController.addScreen(new MockScreen(false /* shouldShowScreen */,
+                false /* canSkipScreen */));
+        mController.addScreen(new MockScreen(true /* shouldShowScreen */,
+                false /* canSkipScreen */));
+        mController.showNextScreen();
+        assertEquals(1, mOnboardUi.currentScreen);
+    }
+
+    @Suppress
+    public void testScreenRequired() {
+        final MockScreen mockScreen =
+                new MockScreen(true /* shouldShowScreen */, false /* canSkipScreen */);
+        mController.addScreen(mockScreen);
+
+        mController.showNextScreen();
+        assertEquals(0, mOnboardUi.currentScreen);
+        assertFalse(mOnboardUi.completedOnboardingFlow);
+
+        // User tried to skip an unskippable screen
+        mController.onScreenResult(0, false);
+        assertEquals(0, mOnboardUi.currentScreen);
+        assertFalse(mOnboardUi.completedOnboardingFlow);
+
+        // User said yes, but the underlying requirements have not been fulfilled yet, so don't
+        // show the next screen. Should be very rare in practice.
+        mController.onScreenResult(0, true);
+        assertEquals(0, mOnboardUi.currentScreen);
+        assertFalse(mOnboardUi.completedOnboardingFlow);
+
+        // Requirement has been fulfiled.
+        mockScreen.shouldShowScreen = false;
+        mController.onScreenResult(0, true);
+        assertTrue(mOnboardUi.completedOnboardingFlow);
+    }
+
+    /**
+     * Verifies the use case where completing the first screen will provide the necessary conditions
+     * to skip the second screen as well.
+     *
+     * For example, setting the default dialer in the first screen will automatically grant
+     * permissions such that the second permissions screen is no longer needed.
+     */
+    @Suppress
+    public void testFirstScreenCompleted_SkipsSecondScreen() {
+        final MockScreen mockScreen1 =
+                new MockScreen(true /* shouldShowScreen */, true /* canSkipScreen */);
+        final MockScreen mockScreen2 =
+                new MockScreen(true /* shouldShowScreen */, false /* canSkipScreen */);
+        mController.addScreen(mockScreen1);
+        mController.addScreen(mockScreen2);
+
+        mController.showNextScreen();
+        assertEquals(0, mOnboardUi.currentScreen);
+        assertFalse(mOnboardUi.completedOnboardingFlow);
+
+        // Screen 1 succeeded, screen 2 is no longer necessary
+        mockScreen2.shouldShowScreen = false;
+        mController.onScreenResult(0, true);
+        assertEquals(0, mOnboardUi.currentScreen);
+        assertTrue(mOnboardUi.completedOnboardingFlow);
+    }
+
+    /**
+     * Verifies the use case where skipping the first screen will proceed to show the second screen
+     * since the necessary conditions to skip the second screen have not been met.
+     */
+    @Suppress
+    public void testFirstScreenSkipped_ShowsSecondScreen() {
+        final MockScreen mockScreen1 =
+                new MockScreen(true /* shouldShowScreen */, true /* canSkipScreen */);
+        final MockScreen mockScreen2 =
+                new MockScreen(true /* shouldShowScreen */, false /* canSkipScreen */);
+        mController.addScreen(mockScreen1);
+        mController.addScreen(mockScreen2);
+
+        mController.showNextScreen();
+        assertEquals(0, mOnboardUi.currentScreen);
+        assertFalse(mOnboardUi.completedOnboardingFlow);
+
+        // Screen 1 skipped
+        mController.onScreenResult(0, false);
+        assertEquals(1, mOnboardUi.currentScreen);
+        assertFalse(mOnboardUi.completedOnboardingFlow);
+
+        // Repeatedly trying to skip screen 2 will not work since it is marked as unskippable.
+        mController.onScreenResult(1, false);
+        assertEquals(1, mOnboardUi.currentScreen);
+        assertFalse(mOnboardUi.completedOnboardingFlow);
+    }
+}
diff --git a/tests/src/com/android/dialer/onboard/PermissionsOnboardScreenTest.java b/tests/src/com/android/dialer/onboard/PermissionsOnboardScreenTest.java
new file mode 100644
index 0000000..ff5e3d5
--- /dev/null
+++ b/tests/src/com/android/dialer/onboard/PermissionsOnboardScreenTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dialer.onboard;
+
+import static org.mockito.Mockito.when;
+
+import android.test.AndroidTestCase;
+
+import com.android.dialer.onboard.OnboardingActivity.PermissionsOnboardingScreen;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class PermissionsOnboardScreenTest extends AndroidTestCase {
+    private PermissionsOnboardingScreen mScreen;
+    @Mock private PermissionsChecker mPermissionsChecker;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        mScreen = new PermissionsOnboardingScreen(mPermissionsChecker);
+    }
+
+    public void testMissingContactsAndPhonePermissions_shouldShowScreen() {
+        when(mPermissionsChecker.hasContactsPermissions()).thenReturn(false);
+        when(mPermissionsChecker.hasPhonePermissions()).thenReturn(false);
+        assertTrue(mScreen.shouldShowScreen());
+    }
+
+    public void testMissingContactsPermission_shouldShowScreen() {
+        when(mPermissionsChecker.hasContactsPermissions()).thenReturn(false);
+        when(mPermissionsChecker.hasPhonePermissions()).thenReturn(true);
+        assertTrue(mScreen.shouldShowScreen());
+    }
+
+    public void testMissingPhonePermission_shouldShowScreen() {
+        when(mPermissionsChecker.hasContactsPermissions()).thenReturn(true);
+        when(mPermissionsChecker.hasPhonePermissions()).thenReturn(false);
+        assertTrue(mScreen.shouldShowScreen());
+    }
+
+    public void testHasAllPermissions_shouldNotShowScreen() {
+        when(mPermissionsChecker.hasContactsPermissions()).thenReturn(true);
+        when(mPermissionsChecker.hasPhonePermissions()).thenReturn(true);
+        assertFalse(mScreen.shouldShowScreen());
+    }
+
+    public void testCanSkipScreen() {
+        assertFalse(mScreen.canSkipScreen());
+    }
+}
diff --git a/tests/src/com/android/dialer/tests/calllog/FillCallLogTestActivity.java b/tests/src/com/android/dialer/tests/calllog/FillCallLogTestActivity.java
index aa2d265..ae0dba8 100644
--- a/tests/src/com/android/dialer/tests/calllog/FillCallLogTestActivity.java
+++ b/tests/src/com/android/dialer/tests/calllog/FillCallLogTestActivity.java
@@ -40,6 +40,8 @@
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.text.Editable;
+import android.text.TextWatcher;
 import android.text.format.DateFormat;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -55,6 +57,7 @@
 import android.widget.Toast;
 
 import com.android.dialer.tests.R;
+import com.android.dialer.util.AppCompatConstants;
 
 import java.util.Calendar;
 import java.util.List;
@@ -70,7 +73,9 @@
 
     private static final Random RNG = new Random();
     private static final int[] CALL_TYPES = new int[] {
-        Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE, Calls.MISSED_TYPE,
+        AppCompatConstants.CALLS_INCOMING_TYPE,
+        AppCompatConstants.CALLS_OUTGOING_TYPE,
+        AppCompatConstants.CALLS_MISSED_TYPE
     };
 
     private TextView mNumberTextView;
@@ -151,11 +156,21 @@
         mAccount0 = (RadioButton) findViewById(R.id.account0);
         mAccount1 = (RadioButton) findViewById(R.id.account1);
 
-        mCustomCallTypeTextView.setOnTouchListener(new View.OnTouchListener() {
+        mCustomCallTypeTextView.addTextChangedListener(new TextWatcher() {
             @Override
-            public boolean onTouch(View v, MotionEvent event) {
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                // Do nothing.
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                // Toggle the custom call type radio button if the text is changed/focused.
                 mCallTypeCustom.toggle();
-                return false;
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+                // Do nothing.
             }
         });
 
@@ -394,15 +409,15 @@
      */
     private int getManualCallType() {
         if (mCallTypeIncoming.isChecked()) {
-            return Calls.INCOMING_TYPE;
+            return AppCompatConstants.CALLS_INCOMING_TYPE;
         } else if (mCallTypeOutgoing.isChecked()) {
-            return Calls.OUTGOING_TYPE;
+            return AppCompatConstants.CALLS_OUTGOING_TYPE;
         } else if (mCallTypeVoicemail.isChecked()) {
-            return Calls.VOICEMAIL_TYPE;
+            return AppCompatConstants.CALLS_VOICEMAIL_TYPE;
         } else if (mCallTypeCustom.isChecked()) {
             return Integer.parseInt(mCustomCallTypeTextView.getText().toString());
         } else {
-            return Calls.MISSED_TYPE;
+            return AppCompatConstants.CALLS_MISSED_TYPE;
         }
     }
 
@@ -524,7 +539,7 @@
             dataUsage = (long) RNG.nextInt(52428800);
         }
 
-        if (getManualCallType() == Calls.VOICEMAIL_TYPE) {
+        if (getManualCallType() == AppCompatConstants.CALLS_VOICEMAIL_TYPE) {
             addManualVoicemail(dateTime.getTimeInMillis());
         } else {
             addCall(mPhoneNumber.getText().toString(), getManualPresentation(),
@@ -594,7 +609,7 @@
         values.put("phone_account_address", accountAddress);
         values.put(Calls.NEW, Integer.valueOf(1));
 
-        if (callType == Calls.MISSED_TYPE) {
+        if (callType == AppCompatConstants.CALLS_MISSED_TYPE) {
             values.put(Calls.IS_READ, 0);
         }
 
diff --git a/tests/src/com/android/dialer/voicemail/VoicemailPlaybackTest.java b/tests/src/com/android/dialer/voicemail/VoicemailPlaybackTest.java
index 420a17c..3a74f73 100644
--- a/tests/src/com/android/dialer/voicemail/VoicemailPlaybackTest.java
+++ b/tests/src/com/android/dialer/voicemail/VoicemailPlaybackTest.java
@@ -111,12 +111,9 @@
             @Override
             public void run() {
                 mPresenter.resumePlayback();
+                assertStateTextContains("Loading voicemail");
             }
         });
-        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
-        getInstrumentation().waitForIdleSync();
-
-        assertStateTextContains("Loading voicemail");
     }
 
     public void testWhenCheckForContentCompletes() throws Throwable {
@@ -132,8 +129,7 @@
         mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
         getInstrumentation().waitForIdleSync();
 
-        // Since the content is already fetched, don't show the loading message.
-        assertStateTextNotContains("Loading voicemail");
+        assertStateTextContains("Loading voicemail");
     }
 
     public void testInvalidVoicemailShowsErrorMessage() throws Throwable {
@@ -232,12 +228,12 @@
         }
     }
 
-    private void assertStateTextContains(String text) throws Throwable {
+    private void assertStateTextContains(String text) {
         assertNotNull(mLayout);
         assertTrue(mLayout.getStateText().contains(text));
     }
 
-    private void assertStateTextNotContains(String text) throws Throwable {
+    private void assertStateTextNotContains(String text) {
         assertNotNull(mLayout);
         assertFalse(mLayout.getStateText().contains(text));
     }