Merge "Use ContactsUtil.FLAG_N_FEATURE in ContactInfoHelper" into ub-contactsdialer-b-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8254f36..9d5af42 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -17,8 +17,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.dialer"
     coreApp="true"
-    android:versionCode="20311"
-    android:versionName="2.3.11">
+    android:versionCode="20314"
+    android:versionName="2.3.14">
 
     <uses-sdk
         android:minSdkVersion="23"
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 045837f..0298023 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -17,10 +17,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="applicationLabel" msgid="7762561155467201526">"Telefon"</string>
-    <string name="launcherActivityLabel" msgid="1129729740601172692">"Telefon"</string>
+    <string name="applicationLabel" msgid="7762561155467201526">"Opkald"</string>
+    <string name="launcherActivityLabel" msgid="1129729740601172692">"Opkald"</string>
     <string name="launcherDialpadActivityLabel" msgid="3959809805046059167">"Telefontastatur"</string>
-    <string name="dialerIconLabel" msgid="6500826552823403796">"Telefon"</string>
+    <string name="dialerIconLabel" msgid="6500826552823403796">"Opkald"</string>
     <string name="callHistoryIconLabel" msgid="3734413397291301223">"Opkaldshistorik"</string>
     <string name="action_report_number" msgid="4635403959812186162">"Rapportér et forkert nummer"</string>
     <string name="action_copy_number_text" msgid="588249522108594155">"Kopiér nummeret"</string>
diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java
index 34e49a9..c045967 100644
--- a/src/com/android/dialer/CallDetailActivity.java
+++ b/src/com/android/dialer/CallDetailActivity.java
@@ -43,6 +43,7 @@
 import com.android.contacts.common.ContactPhotoManager;
 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
 import com.android.contacts.common.GeoUtil;
+import com.android.contacts.common.compat.CompatUtils;
 import com.android.contacts.common.interactions.TouchPointManager;
 import com.android.contacts.common.preference.ContactsPreferences;
 import com.android.contacts.common.testing.NeededForTesting;
@@ -249,7 +250,9 @@
 
         mQuickContactBadge = (QuickContactBadge) findViewById(R.id.quick_contact_photo);
         mQuickContactBadge.setOverlay(null);
-        mQuickContactBadge.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
+        if (CompatUtils.hasPrioritizedMimeType()) {
+            mQuickContactBadge.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
+        }
         mCallerName = (TextView) findViewById(R.id.caller_name);
         mCallerNumber = (TextView) findViewById(R.id.caller_number);
         mAccountLabel = (TextView) findViewById(R.id.phone_account_label);
diff --git a/src/com/android/dialer/SpecialCharSequenceMgr.java b/src/com/android/dialer/SpecialCharSequenceMgr.java
index 6ca5ef5..d9e2938 100644
--- a/src/com/android/dialer/SpecialCharSequenceMgr.java
+++ b/src/com/android/dialer/SpecialCharSequenceMgr.java
@@ -192,7 +192,6 @@
      * and query cancel handler implemented in {@link SimContactQueryCookie}.
      */
     static boolean handleAdnEntry(Context context, String input, EditText textField) {
-        context = context.getApplicationContext();
         /* ADN entries are of the form "N(N)(N)#" */
         TelephonyManager telephonyManager =
                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
@@ -225,7 +224,7 @@
                 // the dialer text field.
 
                 // create the async query handler
-                final QueryHandler handler = new QueryHandler (context.getContentResolver());
+                final QueryHandler handler = new QueryHandler(context.getContentResolver());
 
                 // create the cookie object
                 final SimContactQueryCookie sc = new SimContactQueryCookie(index - 1, handler,
@@ -248,16 +247,17 @@
                 List<PhoneAccountHandle> subscriptionAccountHandles =
                         PhoneAccountUtils.getSubscriptionPhoneAccounts(context);
 
+                Context applicationContext = context.getApplicationContext();
                 boolean hasUserSelectedDefault = subscriptionAccountHandles.contains(
-                        TelecomUtil.getDefaultOutgoingPhoneAccount(context,
+                        TelecomUtil.getDefaultOutgoingPhoneAccount(applicationContext,
                                 PhoneAccount.SCHEME_TEL));
 
                 if (subscriptionAccountHandles.size() == 1 || hasUserSelectedDefault) {
-                    Uri uri = TelecomUtil.getAdnUriForPhoneAccount(context, null);
+                    Uri uri = TelecomUtil.getAdnUriForPhoneAccount(applicationContext, null);
                     handleAdnQuery(handler, sc, uri);
                 } else if (subscriptionAccountHandles.size() > 1){
-                    SelectPhoneAccountListener callback =
-                            new HandleAdnEntryAccountSelectedCallback(context, handler, sc);
+                    SelectPhoneAccountListener callback = new HandleAdnEntryAccountSelectedCallback(
+                            applicationContext, handler, sc);
 
                     DialogFragment dialogFragment = SelectPhoneAccountDialogFragment.newInstance(
                             subscriptionAccountHandles, callback);
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index ea2dbd2..dac50ff 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -41,10 +41,12 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
 import com.android.contacts.common.preference.ContactsPreferences;
 import com.android.contacts.common.util.PermissionsUtil;
 import com.android.dialer.PhoneCallDetails;
 import com.android.dialer.R;
+import com.android.dialer.calllog.calllogcache.CallLogCache;
 import com.android.dialer.contactinfo.ContactInfoCache;
 import com.android.dialer.contactinfo.ContactInfoCache.OnContactInfoChangedListener;
 import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
@@ -131,8 +133,8 @@
     /** Instance of helper class for managing views. */
     private final CallLogListItemHelper mCallLogListItemHelper;
 
-    /** Cache for repeated requests to TelecomManager. */
-    protected final TelecomCallLogCache mTelecomCallLogCache;
+    /** Cache for repeated requests to Telecom/Telephony. */
+    protected final CallLogCache mCallLogCache;
 
     /** Helper to group call log entries. */
     private final CallLogGroupBuilder mCallLogGroupBuilder;
@@ -260,11 +262,12 @@
         Resources resources = mContext.getResources();
         CallTypeHelper callTypeHelper = new CallTypeHelper(resources);
 
-        mTelecomCallLogCache = new TelecomCallLogCache(mContext);
+        mCallLogCache = CallLogCache.getCallLogCache(mContext);
+
         PhoneCallDetailsHelper phoneCallDetailsHelper =
-                new PhoneCallDetailsHelper(mContext, resources, mTelecomCallLogCache);
+                new PhoneCallDetailsHelper(mContext, resources, mCallLogCache);
         mCallLogListItemHelper =
-                new CallLogListItemHelper(phoneCallDetailsHelper, resources, mTelecomCallLogCache);
+                new CallLogListItemHelper(phoneCallDetailsHelper, resources, mCallLogCache);
         mCallLogGroupBuilder = new CallLogGroupBuilder(this);
         mFilteredNumberAsyncQueryHandler =
                 new FilteredNumberAsyncQueryHandler(mContext.getContentResolver());
@@ -331,7 +334,7 @@
     @VisibleForTesting
     /* package */ void pauseCache() {
         mContactInfoCache.stop();
-        mTelecomCallLogCache.reset();
+        mCallLogCache.reset();
     }
 
     @Override
@@ -360,7 +363,7 @@
                 view,
                 mContext,
                 mExpandCollapseListener,
-                mTelecomCallLogCache,
+                mCallLogCache,
                 mCallLogListItemHelper,
                 mVoicemailPlaybackPresenter,
                 mFilteredNumberAsyncQueryHandler,
@@ -451,7 +454,7 @@
         final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);
         final ContactInfo cachedContactInfo = ContactInfoHelper.getContactInfo(c);
         final boolean isVoicemailNumber =
-                mTelecomCallLogCache.isVoicemailNumber(accountHandle, number);
+                mCallLogCache.isVoicemailNumber(accountHandle, number);
 
         // Note: Binding of the action buttons is done as required in configureActionViews when the
         // user expands the actions ViewStub.
@@ -463,7 +466,7 @@
                     countryIso, cachedContactInfo);
         }
         CharSequence formattedNumber = info.formattedNumber == null
-                ? null : PhoneNumberUtils.createTtsSpannable(info.formattedNumber);
+                ? null : PhoneNumberUtilsCompat.createTtsSpannable(info.formattedNumber);
 
         final PhoneCallDetails details = new PhoneCallDetails(
                 mContext, number, numberPresentation, formattedNumber,
diff --git a/src/com/android/dialer/calllog/CallLogListItemHelper.java b/src/com/android/dialer/calllog/CallLogListItemHelper.java
index fb2bab8..5d2bc85 100644
--- a/src/com/android/dialer/calllog/CallLogListItemHelper.java
+++ b/src/com/android/dialer/calllog/CallLogListItemHelper.java
@@ -25,6 +25,7 @@
 import com.android.dialer.PhoneCallDetails;
 import com.android.dialer.util.AppCompatConstants;
 import com.android.dialer.R;
+import com.android.dialer.calllog.calllogcache.CallLogCache;
 
 /**
  * Helper class to fill in the views of a call log entry.
@@ -36,21 +37,22 @@
     private final PhoneCallDetailsHelper mPhoneCallDetailsHelper;
     /** Resources to look up strings. */
     private final Resources mResources;
-    private final TelecomCallLogCache mTelecomCallLogCache;
+    private final CallLogCache mCallLogCache;
 
     /**
      * Creates a new helper instance.
      *
      * @param phoneCallDetailsHelper used to set the details of a phone call
-     * @param phoneNumberHelper used to process phone number
+     * @param resources The object from which resources can be retrieved
+     * @param callLogCache A cache for values retrieved from telecom/telephony
      */
     public CallLogListItemHelper(
             PhoneCallDetailsHelper phoneCallDetailsHelper,
             Resources resources,
-            TelecomCallLogCache telecomCallLogCache) {
+            CallLogCache callLogCache) {
         mPhoneCallDetailsHelper = phoneCallDetailsHelper;
         mResources = resources;
-        mTelecomCallLogCache = telecomCallLogCache;
+        mCallLogCache = callLogCache;
     }
 
     /**
@@ -187,7 +189,7 @@
         }
 
         int stringID = getCallDescriptionStringID(details.callTypes, details.isRead);
-        String accountLabel = mTelecomCallLogCache.getAccountLabel(details.accountHandle);
+        String accountLabel = mCallLogCache.getAccountLabel(details.accountHandle);
 
         // Use chosen string resource to build up the message.
         CharSequence onAccountLabel = accountLabel == null
diff --git a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
index 099877f..21f3213 100644
--- a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
+++ b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
@@ -27,7 +27,6 @@
 import android.support.v7.widget.CardView;
 import android.support.v7.widget.RecyclerView;
 import android.telecom.PhoneAccountHandle;
-import android.telephony.PhoneNumberUtils;
 import android.text.BidiFormatter;
 import android.text.TextDirectionHeuristics;
 import android.text.TextUtils;
@@ -43,11 +42,14 @@
 import com.android.contacts.common.ClipboardUtils;
 import com.android.contacts.common.ContactPhotoManager;
 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
+import com.android.contacts.common.compat.CompatUtils;
+import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
 import com.android.contacts.common.dialog.CallSubjectDialog;
 import com.android.contacts.common.testing.NeededForTesting;
 import com.android.contacts.common.util.UriUtils;
 import com.android.dialer.DialtactsActivity;
 import com.android.dialer.R;
+import com.android.dialer.calllog.calllogcache.CallLogCache;
 import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
 import com.android.dialer.filterednumber.BlockNumberDialogFragment;
 import com.android.dialer.filterednumber.FilteredNumbersUtil;
@@ -188,7 +190,7 @@
     public ContactInfo info;
 
     private final Context mContext;
-    private final TelecomCallLogCache mTelecomCallLogCache;
+    private final CallLogCache mCallLogCache;
     private final CallLogListItemHelper mCallLogListItemHelper;
     private final VoicemailPlaybackPresenter mVoicemailPlaybackPresenter;
     private final FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler;
@@ -202,7 +204,7 @@
     private CallLogListItemViewHolder(
             Context context,
             View.OnClickListener expandCollapseListener,
-            TelecomCallLogCache telecomCallLogCache,
+            CallLogCache callLogCache,
             CallLogListItemHelper callLogListItemHelper,
             VoicemailPlaybackPresenter voicemailPlaybackPresenter,
             FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler,
@@ -218,7 +220,7 @@
 
         mContext = context;
         mExpandCollapseListener = expandCollapseListener;
-        mTelecomCallLogCache = telecomCallLogCache;
+        mCallLogCache = callLogCache;
         mCallLogListItemHelper = callLogListItemHelper;
         mVoicemailPlaybackPresenter = voicemailPlaybackPresenter;
         mFilteredNumberAsyncQueryHandler = filteredNumberAsyncQueryHandler;
@@ -240,8 +242,9 @@
         phoneCallDetailsViews.callLocationAndDate.setElegantTextHeight(false);
 
         quickContactView.setOverlay(null);
-        quickContactView.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
-
+        if (CompatUtils.hasPrioritizedMimeType()) {
+            quickContactView.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
+        }
         primaryActionButtonView.setOnClickListener(this);
         primaryActionView.setOnClickListener(mExpandCollapseListener);
         primaryActionView.setOnCreateContextMenuListener(this);
@@ -251,7 +254,7 @@
             View view,
             Context context,
             View.OnClickListener expandCollapseListener,
-            TelecomCallLogCache telecomCallLogCache,
+            CallLogCache callLogCache,
             CallLogListItemHelper callLogListItemHelper,
             VoicemailPlaybackPresenter voicemailPlaybackPresenter,
             FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler,
@@ -260,7 +263,7 @@
         return new CallLogListItemViewHolder(
                 context,
                 expandCollapseListener,
-                telecomCallLogCache,
+                callLogCache,
                 callLogListItemHelper,
                 voicemailPlaybackPresenter,
                 filteredNumberAsyncQueryHandler,
@@ -284,7 +287,7 @@
         if (callType == CallLog.Calls.VOICEMAIL_TYPE) {
             menu.setHeaderTitle(mContext.getResources().getText(R.string.voicemail));
         } else {
-            menu.setHeaderTitle(PhoneNumberUtils.createTtsSpannable(
+            menu.setHeaderTitle(PhoneNumberUtilsCompat.createTtsSpannable(
                     BidiFormatter.getInstance().unicodeWrap(number, TextDirectionHeuristics.LTR)));
         }
 
@@ -298,7 +301,7 @@
         // 3) Number is a SIP address
 
         if (PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation)
-                && !mTelecomCallLogCache.isVoicemailNumber(accountHandle, number)
+                && !mCallLogCache.isVoicemailNumber(accountHandle, number)
                 && !PhoneNumberUtil.isSipNumber(number)) {
             menu.add(ContextMenu.NONE, R.id.context_menu_edit_before_call, ContextMenu.NONE,
                     R.string.action_edit_number_before_call)
@@ -419,7 +422,7 @@
             // Treat as normal list item; show call button, if possible.
             if (PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation)) {
                 boolean isVoicemailNumber =
-                        mTelecomCallLogCache.isVoicemailNumber(accountHandle, number);
+                        mCallLogCache.isVoicemailNumber(accountHandle, number);
                 if (isVoicemailNumber) {
                     // Call to generic voicemail number, in case there are multiple accounts.
                     primaryActionButtonView.setTag(
@@ -468,7 +471,7 @@
         }
 
         // If one of the calls had video capabilities, show the video call button.
-        if (mTelecomCallLogCache.isVideoEnabled() && canPlaceCallToNumber &&
+        if (mCallLogCache.isVideoEnabled() && canPlaceCallToNumber &&
                 phoneCallDetailsViews.callTypeIcons.isVideoShown()) {
             videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number));
             videoCallButtonView.setVisibility(View.VISIBLE);
@@ -522,9 +525,9 @@
         mCallLogListItemHelper.setActionContentDescriptions(this);
 
         boolean supportsCallSubject =
-                mTelecomCallLogCache.doesAccountSupportCallSubject(accountHandle);
+                mCallLogCache.doesAccountSupportCallSubject(accountHandle);
         boolean isVoicemailNumber =
-                mTelecomCallLogCache.isVoicemailNumber(accountHandle, number);
+                mCallLogCache.isVoicemailNumber(accountHandle, number);
         callWithNoteButtonView.setVisibility(
                 supportsCallSubject && !isVoicemailNumber ? View.VISIBLE : View.GONE);
     }
@@ -570,7 +573,7 @@
     public void updatePhoto() {
         quickContactView.assignContactUri(info.lookupUri);
 
-        final boolean isVoicemail = mTelecomCallLogCache.isVoicemailNumber(accountHandle, number);
+        final boolean isVoicemail = mCallLogCache.isVoicemailNumber(accountHandle, number);
         int contactType = ContactPhotoManager.TYPE_DEFAULT;
         if (isVoicemail) {
             contactType = ContactPhotoManager.TYPE_VOICEMAIL;
@@ -626,15 +629,16 @@
     @NeededForTesting
     public static CallLogListItemViewHolder createForTest(Context context) {
         Resources resources = context.getResources();
-        TelecomCallLogCache telecomCallLogCache = new TelecomCallLogCache(context);
+        CallLogCache callLogCache =
+                CallLogCache.getCallLogCache(context);
         PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper(
-                context, resources, telecomCallLogCache);
+                context, resources, callLogCache);
 
         CallLogListItemViewHolder viewHolder = new CallLogListItemViewHolder(
                 context,
                 null /* expandCollapseListener */,
-                telecomCallLogCache,
-                new CallLogListItemHelper(phoneCallDetailsHelper, resources, telecomCallLogCache),
+                callLogCache,
+                new CallLogListItemHelper(phoneCallDetailsHelper, resources, callLogCache),
                 null /* voicemailPlaybackPresenter */,
                 null /* filteredNumberAsyncQueryHandler */,
                 null /* filteredNumberDialogCallback */,
diff --git a/src/com/android/dialer/calllog/CallLogQuery.java b/src/com/android/dialer/calllog/CallLogQuery.java
index 3f79801..905a4b7 100644
--- a/src/com/android/dialer/calllog/CallLogQuery.java
+++ b/src/com/android/dialer/calllog/CallLogQuery.java
@@ -18,10 +18,12 @@
 
 import android.provider.CallLog.Calls;
 
+import com.android.dialer.compat.DialerCompatUtils;
 import com.android.dialer.util.AppCompatConstants;
 
-import java.util.ArrayList;
-import java.util.Arrays;
+import com.google.common.collect.Lists;
+
+import java.util.List;
 
 /**
  * The query for the call log table.
@@ -52,7 +54,6 @@
             Calls.FEATURES,                     // 20
             Calls.DATA_USAGE,                   // 21
             Calls.TRANSCRIPTION,                // 22
-            Calls.CACHED_PHOTO_URI              // 23
     };
 
     public static final int ID = 0;
@@ -78,18 +79,32 @@
     public static final int FEATURES = 20;
     public static final int DATA_USAGE = 21;
     public static final int TRANSCRIPTION = 22;
-    public static final int CACHED_PHOTO_URI = 23;
-    public static final int POST_DIAL_DIGITS = 24;
+
+    // Indices for columns that may not be available, depending on the Sdk Version
+    /**
+     * Only available in versions >= M
+     * Call {@link DialerCompatUtils#isCallsCachedPhotoUriCompatible()} prior to use
+     */
+    public static int CACHED_PHOTO_URI = -1;
+
+    /**
+     * Only available in versions > M
+     * Call {@link PhoneNumberDisplayUtil#canShowPostDial()} prior to use
+     */
+    public static int POST_DIAL_DIGITS = -1;
 
     public static final String[] _PROJECTION;
 
     static {
-        ArrayList<String> projectionList = new ArrayList<String>();
-        projectionList.addAll(Arrays.asList(_PROJECTION_INTERNAL));
+        List<String> projectionList = Lists.newArrayList(_PROJECTION_INTERNAL);
+        if (DialerCompatUtils.isCallsCachedPhotoUriCompatible()) {
+            projectionList.add(Calls.CACHED_PHOTO_URI);
+            CACHED_PHOTO_URI = projectionList.size() - 1;
+        }
         if (PhoneNumberDisplayUtil.canShowPostDial()) {
             projectionList.add(AppCompatConstants.POST_DIAL_DIGITS);
+            POST_DIAL_DIGITS = projectionList.size() - 1;
         }
-        projectionList.trimToSize();
         _PROJECTION = projectionList.toArray(new String[projectionList.size()]);
     }
 
diff --git a/src/com/android/dialer/calllog/CallLogQueryHandler.java b/src/com/android/dialer/calllog/CallLogQueryHandler.java
index 3efdce7..4cb835b 100644
--- a/src/com/android/dialer/calllog/CallLogQueryHandler.java
+++ b/src/com/android/dialer/calllog/CallLogQueryHandler.java
@@ -26,6 +26,7 @@
 import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteFullException;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -34,9 +35,9 @@
 import android.provider.VoicemailContract.Voicemails;
 import android.util.Log;
 
+import com.android.contacts.common.compat.SdkVersionOverride;
 import com.android.contacts.common.database.NoNullCursorAsyncQueryHandler;
 import com.android.contacts.common.util.PermissionsUtil;
-import com.android.dialer.filterednumber.FilteredNumbersUtil;
 import com.android.dialer.util.AppCompatConstants;
 import com.android.dialer.util.TelecomUtil;
 import com.android.dialer.voicemail.VoicemailStatusHelperImpl;
@@ -48,8 +49,6 @@
 
 /** Handles asynchronous queries to the call log. */
 public class CallLogQueryHandler extends NoNullCursorAsyncQueryHandler {
-    private static final String[] EMPTY_STRING_ARRAY = new String[0];
-
     private static final String TAG = "CallLogQueryHandler";
     private static final int NUM_LOGS_TO_DISPLAY = 1000;
 
@@ -160,25 +159,25 @@
 
     /** Fetches the list of calls in the call log. */
     private void fetchCalls(int token, int callType, boolean newOnly, long newerThan) {
-        // We need to check for NULL explicitly otherwise entries with where READ is NULL
-        // may not match either the query or its negation.
-        // We consider the calls that are not yet consumed (i.e. IS_READ = 0) as "new".
         StringBuilder where = new StringBuilder();
         List<String> selectionArgs = Lists.newArrayList();
 
+        // Always hide blocked calls.
+        where.append("(").append(Calls.TYPE).append(" != ?)");
+        selectionArgs.add(Integer.toString(AppCompatConstants.CALLS_BLOCKED_TYPE));
+
         // Ignore voicemails marked as deleted
-        where.append(Voicemails.DELETED);
-        where.append(" = 0");
+        if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M)
+                >= Build.VERSION_CODES.M) {
+            where.append(" AND (").append(Voicemails.DELETED).append(" = 0)");
+        }
 
         if (newOnly) {
-            where.append(" AND ");
-            where.append(Calls.NEW);
-            where.append(" = 1");
+            where.append(" AND (").append(Calls.NEW).append(" = 1)");
         }
 
         if (callType > CALL_TYPE_ALL) {
-            where.append(" AND ");
-            where.append("(" + Calls.TYPE + " = ?)");
+            where.append(" AND (").append(Calls.TYPE).append(" = ?)");
             selectionArgs.add(Integer.toString(callType));
         } else {
             where.append(" AND NOT ");
@@ -186,24 +185,17 @@
         }
 
         if (newerThan > 0) {
-            where.append(" AND ");
-            where.append("(" + Calls.DATE + " > ?)");
+            where.append(" AND (").append(Calls.DATE).append(" > ?)");
             selectionArgs.add(Long.toString(newerThan));
         }
 
-        // Always hide blocked calls.
-        where.append(" AND ");
-        where.append("(" + Calls.TYPE + " != ?)");
-        selectionArgs.add(Integer.toString(AppCompatConstants.CALLS_BLOCKED_TYPE));
-
         final int limit = (mLogLimit == -1) ? NUM_LOGS_TO_DISPLAY : mLogLimit;
         final String selection = where.length() > 0 ? where.toString() : null;
         Uri uri = TelecomUtil.getCallLogUri(mContext).buildUpon()
                 .appendQueryParameter(Calls.LIMIT_PARAM_KEY, Integer.toString(limit))
                 .build();
-        startQuery(token, null, uri,
-                CallLogQuery._PROJECTION, selection, selectionArgs.toArray(EMPTY_STRING_ARRAY),
-                Calls.DEFAULT_SORT_ORDER);
+        startQuery(token, null, uri, CallLogQuery._PROJECTION, selection, selectionArgs.toArray(
+                new String[selectionArgs.size()]), Calls.DEFAULT_SORT_ORDER);
     }
 
     /** Cancel any pending fetch request. */
diff --git a/src/com/android/dialer/calllog/ContactInfoHelper.java b/src/com/android/dialer/calllog/ContactInfoHelper.java
index 44c97ad..2ecb1e8 100644
--- a/src/com/android/dialer/calllog/ContactInfoHelper.java
+++ b/src/com/android/dialer/calllog/ContactInfoHelper.java
@@ -35,6 +35,7 @@
 import com.android.contacts.common.util.PermissionsUtil;
 import com.android.contacts.common.util.PhoneNumberHelper;
 import com.android.contacts.common.util.UriUtils;
+import com.android.dialer.compat.DialerCompatUtils;
 import com.android.dialer.service.CachedNumberLookupService;
 import com.android.dialer.service.CachedNumberLookupService.CachedContactInfo;
 import com.android.dialer.util.TelecomUtil;
@@ -159,9 +160,14 @@
             return ContactInfo.EMPTY;
         }
 
-        Cursor phoneLookupCursor = mContext.getContentResolver().query(uri,
-                PhoneQuery.PHONE_LOOKUP_PROJECTION, null, null, null);
-
+        Cursor phoneLookupCursor = null;
+        try {
+            phoneLookupCursor = mContext.getContentResolver().query(uri,
+                    PhoneQuery.PHONE_LOOKUP_PROJECTION, null, null, null);
+        } catch (NullPointerException e) {
+            // Trap NPE from pre-N CP2
+            return null;
+        }
         if (phoneLookupCursor == null) {
             return null;
         }
@@ -332,7 +338,8 @@
 
             final Uri updatedPhotoUriContactsOnly =
                     UriUtils.nullForNonContactsUri(updatedInfo.photoUri);
-            if (!UriUtils.areEqual(updatedPhotoUriContactsOnly, callLogInfo.photoUri)) {
+            if (DialerCompatUtils.isCallsCachedPhotoUriCompatible() &&
+                    !UriUtils.areEqual(updatedPhotoUriContactsOnly, callLogInfo.photoUri)) {
                 values.put(Calls.CACHED_PHOTO_URI,
                         UriUtils.uriToString(updatedPhotoUriContactsOnly));
                 needsUpdate = true;
@@ -351,8 +358,10 @@
             values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number);
             values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber);
             values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId);
-            values.put(Calls.CACHED_PHOTO_URI, UriUtils.uriToString(
-                    UriUtils.nullForNonContactsUri(updatedInfo.photoUri)));
+            if (DialerCompatUtils.isCallsCachedPhotoUriCompatible()) {
+                values.put(Calls.CACHED_PHOTO_URI, UriUtils.uriToString(
+                        UriUtils.nullForNonContactsUri(updatedInfo.photoUri)));
+            }
             values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber);
             needsUpdate = true;
         }
@@ -427,8 +436,10 @@
 
         info.normalizedNumber = c.getString(CallLogQuery.CACHED_NORMALIZED_NUMBER);
         info.photoId = c.getLong(CallLogQuery.CACHED_PHOTO_ID);
-        info.photoUri = UriUtils.nullForNonContactsUri(
-                UriUtils.parseUriOrNull(c.getString(CallLogQuery.CACHED_PHOTO_URI)));
+        info.photoUri = DialerCompatUtils.isCallsCachedPhotoUriCompatible() ?
+                UriUtils.nullForNonContactsUri(
+                        UriUtils.parseUriOrNull(c.getString(CallLogQuery.CACHED_PHOTO_URI)))
+                : null;
         info.formattedNumber = c.getString(CallLogQuery.CACHED_FORMATTED_NUMBER);
 
         return info;
diff --git a/src/com/android/dialer/calllog/PhoneAccountUtils.java b/src/com/android/dialer/calllog/PhoneAccountUtils.java
index ceadabc..7dc3292 100644
--- a/src/com/android/dialer/calllog/PhoneAccountUtils.java
+++ b/src/com/android/dialer/calllog/PhoneAccountUtils.java
@@ -23,7 +23,6 @@
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 
-import com.android.contacts.common.util.PermissionsUtil;
 import com.android.dialer.util.TelecomUtil;
 
 import java.util.ArrayList;
diff --git a/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java b/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java
index 4d201b0..e6b8508 100644
--- a/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java
+++ b/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java
@@ -35,6 +35,7 @@
 import com.android.contacts.common.util.PhoneNumberHelper;
 import com.android.dialer.PhoneCallDetails;
 import com.android.dialer.R;
+import com.android.dialer.calllog.calllogcache.CallLogCache;
 import com.android.dialer.util.DialerUtils;
 
 import java.util.ArrayList;
@@ -55,7 +56,7 @@
 
     private CharSequence mPhoneTypeLabelForTest;
 
-    private final TelecomCallLogCache mTelecomCallLogCache;
+    private final CallLogCache mCallLogCache;
 
     /** Calendar used to construct dates */
     private final Calendar mCalendar;
@@ -75,10 +76,10 @@
     public PhoneCallDetailsHelper(
             Context context,
             Resources resources,
-            TelecomCallLogCache telecomCallLogCache) {
+            CallLogCache callLogCache) {
         mContext = context;
         mResources = resources;
-        mTelecomCallLogCache = telecomCallLogCache;
+        mCallLogCache = callLogCache;
         mCalendar = Calendar.getInstance();
     }
 
@@ -115,12 +116,12 @@
         setCallCountAndDate(views, callCount, callLocationAndDate);
 
         // Set the account label if it exists.
-        String accountLabel = mTelecomCallLogCache.getAccountLabel(details.accountHandle);
+        String accountLabel = mCallLogCache.getAccountLabel(details.accountHandle);
 
         if (accountLabel != null) {
             views.callAccountLabel.setVisibility(View.VISIBLE);
             views.callAccountLabel.setText(accountLabel);
-            int color = mTelecomCallLogCache.getAccountColor(details.accountHandle);
+            int color = mCallLogCache.getAccountColor(details.accountHandle);
             if (color == PhoneAccount.NO_HIGHLIGHT_COLOR) {
                 int defaultColor = R.color.dialtacts_secondary_text_color;
                 views.callAccountLabel.setTextColor(mContext.getResources().getColor(defaultColor));
@@ -198,7 +199,7 @@
         // Only show a label if the number is shown and it is not a SIP address.
         if (!TextUtils.isEmpty(details.number)
                 && !PhoneNumberHelper.isUriNumber(details.number.toString())
-                && !mTelecomCallLogCache.isVoicemailNumber(details.accountHandle, details.number)) {
+                && !mCallLogCache.isVoicemailNumber(details.accountHandle, details.number)) {
 
             if (TextUtils.isEmpty(details.namePrimary) && !TextUtils.isEmpty(details.geocode)) {
                 numberFormattedLabel = details.geocode;
diff --git a/src/com/android/dialer/calllog/calllogcache/CallLogCache.java b/src/com/android/dialer/calllog/calllogcache/CallLogCache.java
new file mode 100644
index 0000000..e64c012
--- /dev/null
+++ b/src/com/android/dialer/calllog/calllogcache/CallLogCache.java
@@ -0,0 +1,96 @@
+/*
+ * 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.calllog.calllogcache;
+
+import android.content.Context;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.contacts.common.CallUtil;
+import com.android.contacts.common.compat.CompatUtils;
+import com.android.dialer.calllog.CallLogAdapter;
+
+/**
+ * This is the base class for the CallLogCaches.
+ *
+ * Keeps a cache of recently made queries to the Telecom/Telephony processes. The aim of this cache
+ * is to reduce the number of cross-process requests to TelecomManager, which can negatively affect
+ * performance.
+ *
+ * This is designed with the specific use case of the {@link CallLogAdapter} in mind.
+ */
+public abstract class CallLogCache {
+    // TODO: Dialer should be fixed so as not to check isVoicemail() so often but at the time of
+    // this writing, that was a much larger undertaking than creating this cache.
+
+    protected final Context mContext;
+
+    private boolean mHasCheckedForVideoEnabled;
+    private boolean mIsVideoEnabled;
+
+    public CallLogCache(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Return the most compatible version of the TelecomCallLogCache.
+     */
+    public static CallLogCache getCallLogCache(Context context) {
+        if (CompatUtils.isMSIMCompatible()) {
+            return new CallLogCacheLollipopMr1(context);
+        }
+        return new CallLogCacheLollipop(context);
+    }
+
+    public void reset() {
+        mHasCheckedForVideoEnabled = false;
+        mIsVideoEnabled = false;
+    }
+
+    /**
+     * Returns true if the given number is the number of the configured voicemail. To be able to
+     * mock-out this, it is not a static method.
+     */
+    public abstract boolean isVoicemailNumber(PhoneAccountHandle accountHandle,
+            CharSequence number);
+
+    public boolean isVideoEnabled() {
+        if (!mHasCheckedForVideoEnabled) {
+            mIsVideoEnabled = CallUtil.isVideoEnabled(mContext);
+            mHasCheckedForVideoEnabled = true;
+        }
+        return mIsVideoEnabled;
+    }
+
+    /**
+     * Extract account label from PhoneAccount object.
+     */
+    public abstract String getAccountLabel(PhoneAccountHandle accountHandle);
+
+    /**
+     * Extract account color from PhoneAccount object.
+     */
+    public abstract int getAccountColor(PhoneAccountHandle accountHandle);
+
+    /**
+     * Determines if the PhoneAccount supports specifying a call subject (i.e. calling with a note)
+     * for outgoing calls.
+     *
+     * @param accountHandle The PhoneAccount handle.
+     * @return {@code true} if calling with a note is supported, {@code false} otherwise.
+     */
+    public abstract boolean doesAccountSupportCallSubject(PhoneAccountHandle accountHandle);
+}
diff --git a/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipop.java b/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipop.java
new file mode 100644
index 0000000..770cc9d
--- /dev/null
+++ b/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipop.java
@@ -0,0 +1,73 @@
+/*
+ * 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.calllog.calllogcache;
+
+import android.content.Context;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+
+/**
+ * This is a compatibility class for the CallLogCache for versions of dialer before Lollipop Mr1
+ * (the introduction of phone accounts).
+ *
+ * This class should not be initialized directly and instead be acquired from
+ * {@link CallLogCache#getCallLogCache}.
+ */
+class CallLogCacheLollipop extends CallLogCache {
+    private String mVoicemailNumber;
+
+    /* package */ CallLogCacheLollipop(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean isVoicemailNumber(PhoneAccountHandle accountHandle, CharSequence number) {
+        if (TextUtils.isEmpty(number)) {
+            return false;
+        }
+
+        String numberString = number.toString();
+
+        if (!TextUtils.isEmpty(mVoicemailNumber)) {
+            return PhoneNumberUtils.compare(numberString, mVoicemailNumber);
+        }
+
+        if (PhoneNumberUtils.isVoiceMailNumber(numberString)) {
+            mVoicemailNumber = numberString;
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public String getAccountLabel(PhoneAccountHandle accountHandle) {
+        return null;
+    }
+
+    @Override
+    public int getAccountColor(PhoneAccountHandle accountHandle) {
+        return PhoneAccount.NO_HIGHLIGHT_COLOR;
+    }
+
+    @Override
+    public boolean doesAccountSupportCallSubject(PhoneAccountHandle accountHandle) {
+        return false;
+    }
+}
diff --git a/src/com/android/dialer/calllog/TelecomCallLogCache.java b/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipopMr1.java
similarity index 64%
rename from src/com/android/dialer/calllog/TelecomCallLogCache.java
rename to src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipopMr1.java
index 6363b91..d1e3f7b 100644
--- a/src/com/android/dialer/calllog/TelecomCallLogCache.java
+++ b/src/com/android/dialer/calllog/calllogcache/CallLogCacheLollipopMr1.java
@@ -14,67 +14,50 @@
  * limitations under the License
  */
 
-package com.android.dialer.calllog;
+package com.android.dialer.calllog.calllogcache;
 
 import android.content.Context;
-import android.provider.CallLog;
-import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
 import android.text.TextUtils;
-import android.util.Log;
 import android.util.Pair;
 
-import com.android.contacts.common.CallUtil;
-import com.android.contacts.common.util.PhoneNumberHelper;
+import com.android.dialer.calllog.PhoneAccountUtils;
 import com.android.dialer.util.PhoneNumberUtil;
-import com.google.common.collect.Sets;
 
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Set;
 
 /**
- * Keeps a cache of recently made queries to the Telecom process. The aim of this cache is to
- * reduce the number of cross-process requests to TelecomManager, which can negatively affect
- * performance.
+ * This is the CallLogCache for versions of dialer Lollipop Mr1 and above with support for
+ * multi-SIM devices.
  *
- * This is designed with the specific use case of the {@link CallLogAdapter} in mind.
+ * This class should not be initialized directly and instead be acquired from
+ * {@link CallLogCache#getCallLogCache}.
  */
-public class TelecomCallLogCache {
-    private final Context mContext;
-
+class CallLogCacheLollipopMr1 extends CallLogCache {
     // Maps from a phone-account/number pair to a boolean because multiple numbers could return true
     // for the voicemail number if those numbers are not pre-normalized.
-    // TODO: Dialer should be fixed so as not to check isVoicemail() so often but at the time of
-    // this writing, that was a much larger undertaking than creating this cache.
     private final Map<Pair<PhoneAccountHandle, CharSequence>, Boolean> mVoicemailQueryCache =
             new HashMap<>();
     private final Map<PhoneAccountHandle, String> mPhoneAccountLabelCache = new HashMap<>();
     private final Map<PhoneAccountHandle, Integer> mPhoneAccountColorCache = new HashMap<>();
     private final Map<PhoneAccountHandle, Boolean> mPhoneAccountCallWithNoteCache = new HashMap<>();
 
-    private boolean mHasCheckedForVideoEnabled;
-    private boolean mIsVideoEnabled;
-
-    public TelecomCallLogCache(Context context) {
-        mContext = context;
+    /* package */ CallLogCacheLollipopMr1(Context context) {
+        super(context);
     }
 
+    @Override
     public void reset() {
         mVoicemailQueryCache.clear();
         mPhoneAccountLabelCache.clear();
         mPhoneAccountColorCache.clear();
         mPhoneAccountCallWithNoteCache.clear();
 
-        mHasCheckedForVideoEnabled = false;
-        mIsVideoEnabled = false;
+        super.reset();
     }
 
-    /**
-     * Returns true if the given number is the number of the configured voicemail. To be able to
-     * mock-out this, it is not a static method.
-     */
+    @Override
     public boolean isVoicemailNumber(PhoneAccountHandle accountHandle, CharSequence number) {
         if (TextUtils.isEmpty(number)) {
             return false;
@@ -91,9 +74,7 @@
         }
     }
 
-    /**
-     * Extract account label from PhoneAccount object.
-     */
+    @Override
     public String getAccountLabel(PhoneAccountHandle accountHandle) {
         if (mPhoneAccountLabelCache.containsKey(accountHandle)) {
             return mPhoneAccountLabelCache.get(accountHandle);
@@ -104,9 +85,7 @@
         }
     }
 
-    /**
-     * Extract account color from PhoneAccount object.
-     */
+    @Override
     public int getAccountColor(PhoneAccountHandle accountHandle) {
         if (mPhoneAccountColorCache.containsKey(accountHandle)) {
             return mPhoneAccountColorCache.get(accountHandle);
@@ -117,21 +96,7 @@
         }
     }
 
-    public boolean isVideoEnabled() {
-        if (!mHasCheckedForVideoEnabled) {
-            mIsVideoEnabled = CallUtil.isVideoEnabled(mContext);
-            mHasCheckedForVideoEnabled = true;
-        }
-        return mIsVideoEnabled;
-    }
-
-    /**
-     * Determines if the PhoneAccount supports specifying a call subject (i.e. calling with a note)
-     * for outgoing calls.
-     *
-     * @param accountHandle The PhoneAccount handle.
-     * @return {@code true} if calling with a note is supported, {@code false} otherwise.
-     */
+    @Override
     public boolean doesAccountSupportCallSubject(PhoneAccountHandle accountHandle) {
         if (mPhoneAccountCallWithNoteCache.containsKey(accountHandle)) {
             return mPhoneAccountCallWithNoteCache.get(accountHandle);
diff --git a/src/com/android/dialer/compat/CallAudioStateCompat.java b/src/com/android/dialer/compat/CallAudioStateCompat.java
new file mode 100644
index 0000000..51009d0
--- /dev/null
+++ b/src/com/android/dialer/compat/CallAudioStateCompat.java
@@ -0,0 +1,331 @@
+/*
+ * 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.compat;
+
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telecom.CallAudioState;
+
+import com.android.contacts.common.compat.SdkVersionOverride;
+
+import java.util.Locale;
+
+/**
+ * Compatibility class for {@link CallAudioState}
+ */
+public class CallAudioStateCompat {
+
+    /**
+     * Direct the audio stream through the device's earpiece.
+     */
+    public static final int ROUTE_EARPIECE = CallAudioState.ROUTE_EARPIECE;
+
+    /**
+     * Direct the audio stream through Bluetooth.
+     */
+    public static final int ROUTE_BLUETOOTH = CallAudioState.ROUTE_BLUETOOTH;
+
+    /**
+     * Direct the audio stream through a wired headset.
+     */
+    public static final int ROUTE_WIRED_HEADSET = CallAudioState.ROUTE_WIRED_HEADSET;
+
+    /**
+     * Direct the audio stream through the device's speakerphone.
+     */
+    public static final int ROUTE_SPEAKER = CallAudioState.ROUTE_SPEAKER;
+
+    /**
+     * Direct the audio stream through the device's earpiece or wired headset if one is connected.
+     */
+    public static final int ROUTE_WIRED_OR_EARPIECE = CallAudioState.ROUTE_WIRED_OR_EARPIECE;
+
+    private final CallAudioStateImpl mCallAudioState;
+
+    /**
+     * Constructor for a {@link CallAudioStateCompat} object.
+     *
+     * @param muted {@code true} if the call is muted, {@code false} otherwise.
+     * @param route The current audio route being used. Allowed values: {@link #ROUTE_EARPIECE}
+     * {@link #ROUTE_BLUETOOTH} {@link #ROUTE_WIRED_HEADSET} {@link #ROUTE_SPEAKER}
+     * @param supportedRouteMask Bit mask of all routes supported by this call. This should be a
+     * bitwise combination of the following values: {@link #ROUTE_EARPIECE} {@link #ROUTE_BLUETOOTH}
+     * {@link #ROUTE_WIRED_HEADSET} {@link #ROUTE_SPEAKER}
+     */
+    public CallAudioStateCompat(boolean muted, int route, int supportedRouteMask) {
+        if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M)
+                < Build.VERSION_CODES.M) {
+            mCallAudioState = new CallAudioStateBase(muted, route, supportedRouteMask);
+        } else {
+            mCallAudioState = new CallAudioStateMarshmallow(muted, route, supportedRouteMask);
+        }
+    }
+
+    /**
+     * @return {@code true} if the call is muted, {@code false} otherwise.
+     */
+    public boolean isMuted() {
+        return mCallAudioState.isMuted();
+    }
+
+    /**
+     * @return The current audio route being used.
+     */
+    public int getRoute() {
+        return mCallAudioState.getRoute();
+    }
+
+    /**
+     * @return Bit mask of all routes supported by this call.
+     */
+    public int getSupportedRouteMask() {
+        return mCallAudioState.getSupportedRouteMask();
+    }
+
+    /**
+     * Converts the provided audio route into a human readable string representation.
+     *
+     * @param route to convert into a string.
+     * @return String representation of the provided audio route.
+     */
+    public static String audioRouteToString(int route) {
+        if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M)
+                < Build.VERSION_CODES.M) {
+            return CallAudioStateBase.audioRouteToString(route);
+        }
+        return CallAudioStateMarshmallow.audioRouteToString(route);
+    }
+
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        CallAudioStateCompat that = (CallAudioStateCompat) o;
+
+        return mCallAudioState.equals(that.mCallAudioState);
+
+    }
+
+    @Override
+    public int hashCode() {
+        return mCallAudioState.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return mCallAudioState.toString();
+    }
+
+    private interface CallAudioStateImpl {
+        boolean isMuted();
+        int getRoute();
+        int getSupportedRouteMask();
+    }
+
+    /**
+     * CallAudioStateImpl to use if the Sdk version is lower than
+     * {@link android.os.Build.VERSION_CODES.M}
+     *
+     * Coped from {@link android.telecom.CallAudioState}
+     *
+     * Encapsulates the telecom audio state, including the current audio routing, supported audio
+     * routing and mute.
+     */
+    private static class CallAudioStateBase implements CallAudioStateImpl, Parcelable {
+
+        /**
+         * Bit mask of all possible audio routes.
+         */
+        private static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET
+                | ROUTE_SPEAKER;
+
+        private final boolean isMuted;
+        private final int route;
+        private final int supportedRouteMask;
+
+        /**
+         * Constructor for a {@link CallAudioStateBase} object.
+         *
+         * @param muted {@code true} if the call is muted, {@code false} otherwise.
+         * @param route The current audio route being used. Allowed values: {@link #ROUTE_EARPIECE}
+         *      {@link #ROUTE_BLUETOOTH}, {@link #ROUTE_WIRED_HEADSET}, {@link #ROUTE_SPEAKER}
+         * @param supportedRouteMask Bit mask of all routes supported by this call. This should be a
+         *      bitwise combination of the following values: {@link #ROUTE_EARPIECE},
+         *      {@link #ROUTE_BLUETOOTH}, {@link #ROUTE_WIRED_HEADSET}, {@link #ROUTE_SPEAKER}
+         */
+        public CallAudioStateBase(boolean muted, int route, int supportedRouteMask) {
+            this.isMuted = muted;
+            this.route = route;
+            this.supportedRouteMask = supportedRouteMask;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
+            }
+            if (!(obj instanceof CallAudioStateBase)) {
+                return false;
+            }
+            CallAudioStateBase state = (CallAudioStateBase) obj;
+            return isMuted() == state.isMuted() && getRoute() == state.getRoute() &&
+                    getSupportedRouteMask() == state.getSupportedRouteMask();
+        }
+
+        @Override
+        public String toString() {
+            return String.format(Locale.US,
+                    "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s]",
+                    isMuted,
+                    audioRouteToString(route),
+                    audioRouteToString(supportedRouteMask));
+        }
+
+        /**
+         * @return {@code true} if the call is muted, {@code false} otherwise.
+         */
+        @Override
+        public boolean isMuted() {
+            return isMuted;
+        }
+
+        /**
+         * @return The current audio route being used.
+         */
+        public int getRoute() {
+            return route;
+        }
+
+        /**
+         * @return Bit mask of all routes supported by this call.
+         */
+        public int getSupportedRouteMask() {
+            return supportedRouteMask;
+        }
+
+        /**
+         * Converts the provided audio route into a human readable string representation.
+         *
+         * @param route to convert into a string.
+         * @return String representation of the provided audio route.
+         */
+        public static String audioRouteToString(int route) {
+            if (route == 0 || (route & ~ROUTE_ALL) != 0x0) {
+                return "UNKNOWN";
+            }
+
+            StringBuffer buffer = new StringBuffer();
+            if ((route & ROUTE_EARPIECE) == ROUTE_EARPIECE) {
+                listAppend(buffer, "EARPIECE");
+            }
+            if ((route & ROUTE_BLUETOOTH) == ROUTE_BLUETOOTH) {
+                listAppend(buffer, "BLUETOOTH");
+            }
+            if ((route & ROUTE_WIRED_HEADSET) == ROUTE_WIRED_HEADSET) {
+                listAppend(buffer, "WIRED_HEADSET");
+            }
+            if ((route & ROUTE_SPEAKER) == ROUTE_SPEAKER) {
+                listAppend(buffer, "SPEAKER");
+            }
+
+            return buffer.toString();
+        }
+
+        /**
+         * Responsible for creating AudioState objects for deserialized Parcels.
+         */
+        public static final Parcelable.Creator<CallAudioStateBase> CREATOR =
+                new Parcelable.Creator<CallAudioStateBase>() {
+
+                    @Override
+                    public CallAudioStateBase createFromParcel(Parcel source) {
+                        boolean isMuted = source.readByte() == 0 ? false : true;
+                        int route = source.readInt();
+                        int supportedRouteMask = source.readInt();
+                        return new CallAudioStateBase(isMuted, route, supportedRouteMask);
+                    }
+
+                    @Override
+                    public CallAudioStateBase[] newArray(int size) {
+                        return new CallAudioStateBase[size];
+                    }
+                };
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        /**
+         * Writes AudioState object into a serializeable Parcel.
+         */
+        @Override
+        public void writeToParcel(Parcel destination, int flags) {
+            destination.writeByte((byte) (isMuted ? 1 : 0));
+            destination.writeInt(route);
+            destination.writeInt(supportedRouteMask);
+        }
+
+        private static void listAppend(StringBuffer buffer, String str) {
+            if (buffer.length() > 0) {
+                buffer.append(", ");
+            }
+            buffer.append(str);
+        }
+    }
+
+    /**
+     * CallAudioStateImpl to use if the Sdk version is at least
+     * {@link android.os.Build.VERSION_CODES.M}
+     */
+    private static class CallAudioStateMarshmallow implements CallAudioStateImpl {
+
+        private final CallAudioState mCallAudioStateDelegate;
+
+        public CallAudioStateMarshmallow(boolean muted, int route, int supportedRouteMask) {
+            mCallAudioStateDelegate = new CallAudioState(muted, route, supportedRouteMask);
+        }
+
+        @Override
+        public boolean isMuted() {
+            return mCallAudioStateDelegate.isMuted();
+        }
+
+        @Override
+        public int getRoute() {
+            return mCallAudioStateDelegate.getRoute();
+        }
+
+        @Override
+        public int getSupportedRouteMask() {
+            return mCallAudioStateDelegate.getSupportedRouteMask();
+        }
+
+        public static String audioRouteToString(int route) {
+            return CallAudioState.audioRouteToString(route);
+        }
+    }
+}
diff --git a/src/com/android/dialer/compat/DialerCompatUtils.java b/src/com/android/dialer/compat/DialerCompatUtils.java
index 678ffb0..07a279a 100644
--- a/src/com/android/dialer/compat/DialerCompatUtils.java
+++ b/src/com/android/dialer/compat/DialerCompatUtils.java
@@ -30,4 +30,27 @@
         return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.LOLLIPOP)
                 >= Build.VERSION_CODES.M;
     }
+
+    /**
+     * Determines if this version is compatible with a default dialer. Can also force the version to
+     * be lower through SdkVersionOverride.
+     *
+     * @return {@code true} if default dialer is a feature on this device, {@code false} otherwise.
+     */
+    public static boolean isDefaultDialerCompatible() {
+        return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.LOLLIPOP)
+                >= Build.VERSION_CODES.M;
+    }
+
+    /**
+     * Determines if this version has access to the
+     * {@link android.provider.CallLog.Calls.CACHED_PHOTO_URI} column
+     *
+     * @return {@code true} if {@link android.provider.CallLog.Calls.CACHED_PHOTO_URI} is available,
+     * {@code false} otherwise
+     */
+    public static boolean isCallsCachedPhotoUriCompatible() {
+        return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M)
+                >= Build.VERSION_CODES.M;
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/dialer/compat/SettingsCompat.java b/src/com/android/dialer/compat/SettingsCompat.java
new file mode 100644
index 0000000..474a600
--- /dev/null
+++ b/src/com/android/dialer/compat/SettingsCompat.java
@@ -0,0 +1,47 @@
+/*
+ * 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.compat;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.provider.Settings;
+
+import com.android.contacts.common.compat.SdkVersionOverride;
+
+/**
+ * Compatibility class for {@link android.provider.Settings}
+ */
+public class SettingsCompat {
+
+    public static class System {
+
+        /**
+         * Compatibility version of {@link android.provider.Settings.System#canWrite(Context)}
+         *
+         * Note: Since checking preferences at runtime started in M, this method always returns
+         * {@code true} for SDK versions prior to 23. In those versions, the app wouldn't be
+         * installed if it didn't have the proper permission
+         */
+        public static boolean canWrite(Context context) {
+            if (SdkVersionOverride.getSdkVersion(VERSION_CODES.LOLLIPOP) >= Build.VERSION_CODES.M) {
+                return Settings.System.canWrite(context);
+            }
+            return true;
+        }
+    }
+
+}
diff --git a/src/com/android/dialer/filterednumber/BlockedNumbersAdapter.java b/src/com/android/dialer/filterednumber/BlockedNumbersAdapter.java
index e33b0f8..10a4f5a 100644
--- a/src/com/android/dialer/filterednumber/BlockedNumbersAdapter.java
+++ b/src/com/android/dialer/filterednumber/BlockedNumbersAdapter.java
@@ -67,7 +67,7 @@
                         number,
                         countryIso,
                         PhoneNumberUtils.formatNumber(number, countryIso),
-                        R.id.blocked_number_fragment,
+                        R.id.blocked_numbers_activity_container,
                         getFragmentManager(),
                         new BlockNumberDialogFragment.Callback() {
                             @Override
diff --git a/src/com/android/dialer/filterednumber/FilteredNumbersUtil.java b/src/com/android/dialer/filterednumber/FilteredNumbersUtil.java
index 23ce8a8..e3870de 100644
--- a/src/com/android/dialer/filterednumber/FilteredNumbersUtil.java
+++ b/src/com/android/dialer/filterednumber/FilteredNumbersUtil.java
@@ -31,10 +31,6 @@
 import android.text.TextUtils;
 import android.widget.Toast;
 
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
 import com.android.contacts.common.testing.NeededForTesting;
 import com.android.dialer.R;
 import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
@@ -44,6 +40,8 @@
 import com.android.dialer.logging.InteractionEvent;
 import com.android.dialer.logging.Logger;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Utility to help with tasks related to filtered numbers.
  */
@@ -53,11 +51,11 @@
     private static final long RECENT_EMERGENCY_CALL_THRESHOLD_MS = 1000 * 60 * 60 * 24 * 2;
 
     // Pref key for storing the time of end of the last emergency call in milliseconds after epoch.
-    private static final String LAST_EMERGENCY_CALL_MS_PREF_KEY = "last_emergency_call_ms";
+    protected static final String LAST_EMERGENCY_CALL_MS_PREF_KEY = "last_emergency_call_ms";
 
     // Pref key for storing whether a notification has been dispatched to notify the user that call
     // blocking has been disabled because of a recent emergency call.
-    private static final String NOTIFIED_CALL_BLOCKING_DISABLED_BY_EMERGENCY_CALL_PREF_KEY =
+    protected static final String NOTIFIED_CALL_BLOCKING_DISABLED_BY_EMERGENCY_CALL_PREF_KEY =
             "notified_call_blocking_disabled_by_emergency_call";
 
     public static final String CALL_BLOCKING_NOTIFICATION_TAG = "call_blocking";
@@ -241,6 +239,10 @@
             return false;
         }
 
+        if (hasRecentEmergencyCall(context)) {
+            return false;
+        }
+
         final Cursor cursor = context.getContentResolver().query(
                 FilteredNumber.CONTENT_URI,
                 new String[] {
diff --git a/src/com/android/dialer/filterednumber/NumbersAdapter.java b/src/com/android/dialer/filterednumber/NumbersAdapter.java
index 1706107..4e2e578 100644
--- a/src/com/android/dialer/filterednumber/NumbersAdapter.java
+++ b/src/com/android/dialer/filterednumber/NumbersAdapter.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.text.BidiFormatter;
 import android.text.TextDirectionHeuristics;
 import android.text.TextUtils;
@@ -30,6 +31,7 @@
 
 import com.android.contacts.common.ContactPhotoManager;
 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
+import com.android.contacts.common.compat.CompatUtils;
 import com.android.contacts.common.util.UriUtils;
 import com.android.dialer.R;
 import com.android.dialer.calllog.ContactInfo;
@@ -63,9 +65,9 @@
         final QuickContactBadge quickContactBadge =
                 (QuickContactBadge) view.findViewById(R.id.quick_contact_photo);
         quickContactBadge.setOverlay(null);
-        quickContactBadge.setPrioritizedMimeType(
-                ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
-
+        if (CompatUtils.hasPrioritizedMimeType()) {
+            quickContactBadge.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
+        }
         final ContactInfo info = mContactInfoHelper.lookupNumber(number, countryIso);
         final CharSequence locationOrType = getNumberTypeOrLocation(info);
         final String displayNumber = getDisplayNumber(info);
diff --git a/src/com/android/dialer/list/AllContactsFragment.java b/src/com/android/dialer/list/AllContactsFragment.java
index 6ed1fbb..7e76279 100644
--- a/src/com/android/dialer/list/AllContactsFragment.java
+++ b/src/com/android/dialer/list/AllContactsFragment.java
@@ -34,6 +34,7 @@
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 
+import com.android.contacts.common.compat.CompatUtils;
 import com.android.contacts.common.list.ContactEntryListAdapter;
 import com.android.contacts.common.list.ContactEntryListFragment;
 import com.android.contacts.common.list.ContactListFilter;
@@ -152,8 +153,13 @@
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
         final Uri uri = (Uri) view.getTag();
         if (uri != null) {
-            QuickContact.showQuickContact(getContext(), view, uri, null,
-                    Phone.CONTENT_ITEM_TYPE);
+            if (CompatUtils.hasPrioritizedMimeType()) {
+                QuickContact.showQuickContact(getContext(), view, uri, null,
+                        Phone.CONTENT_ITEM_TYPE);
+            } else {
+                QuickContact.showQuickContact(getActivity(), view, uri, QuickContact.MODE_LARGE,
+                        null);
+            }
         }
     }
 
diff --git a/src/com/android/dialer/list/PhoneFavoriteSquareTileView.java b/src/com/android/dialer/list/PhoneFavoriteSquareTileView.java
index c12bed7..69a230c 100644
--- a/src/com/android/dialer/list/PhoneFavoriteSquareTileView.java
+++ b/src/com/android/dialer/list/PhoneFavoriteSquareTileView.java
@@ -24,6 +24,7 @@
 import android.widget.ImageButton;
 import android.widget.TextView;
 
+import com.android.contacts.common.compat.CompatUtils;
 import com.android.contacts.common.list.ContactEntry;
 import com.android.dialer.R;
 
@@ -63,8 +64,13 @@
     }
 
     private void launchQuickContact() {
-        QuickContact.showQuickContact(getContext(), PhoneFavoriteSquareTileView.this,
-                getLookupUri(), null, Phone.CONTENT_ITEM_TYPE);
+        if (CompatUtils.hasPrioritizedMimeType()) {
+            QuickContact.showQuickContact(getContext(), PhoneFavoriteSquareTileView.this,
+                    getLookupUri(), null, Phone.CONTENT_ITEM_TYPE);
+        } else {
+            QuickContact.showQuickContact(getContext(), PhoneFavoriteSquareTileView.this,
+                    getLookupUri(), QuickContact.MODE_LARGE, null);
+        }
     }
 
     @Override
diff --git a/src/com/android/dialer/settings/DefaultRingtonePreference.java b/src/com/android/dialer/settings/DefaultRingtonePreference.java
index a174381..a8a23fd 100644
--- a/src/com/android/dialer/settings/DefaultRingtonePreference.java
+++ b/src/com/android/dialer/settings/DefaultRingtonePreference.java
@@ -16,17 +16,16 @@
 
 package com.android.dialer.settings;
 
-import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.Intent;
 import android.media.RingtoneManager;
 import android.net.Uri;
 import android.preference.RingtonePreference;
-import android.provider.Settings;
 import android.util.AttributeSet;
 import android.widget.Toast;
 
 import com.android.dialer.R;
+import com.android.dialer.compat.SettingsCompat;
 
 /**
  * RingtonePreference which doesn't show default ringtone setting.
@@ -49,7 +48,7 @@
 
     @Override
     protected void onSaveRingtone(Uri ringtoneUri) {
-        if (!Settings.System.canWrite(getContext())) {
+        if (!SettingsCompat.System.canWrite(getContext())) {
             Toast.makeText(
                     getContext(),
                     getContext().getResources().getString(R.string.toast_cannot_write_system_settings),
diff --git a/src/com/android/dialer/settings/DialerSettingsActivity.java b/src/com/android/dialer/settings/DialerSettingsActivity.java
index d0bfbc2..d7b8e11 100644
--- a/src/com/android/dialer/settings/DialerSettingsActivity.java
+++ b/src/com/android/dialer/settings/DialerSettingsActivity.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.UserManager;
 import android.preference.PreferenceManager;
@@ -27,7 +28,9 @@
 import android.view.MenuItem;
 import android.widget.Toast;
 
+import com.android.contacts.common.compat.SdkVersionOverride;
 import com.android.dialer.R;
+import com.android.dialer.compat.SettingsCompat;
 import com.android.dialer.filterednumber.BlockedNumbersSettingsActivity;
 
 import java.util.List;
@@ -54,45 +57,53 @@
         soundSettingsHeader.id = R.id.settings_header_sounds_and_vibration;
         target.add(soundSettingsHeader);
 
-        Header quickResponseSettingsHeader = new Header();
-        Intent quickResponseSettingsIntent =
-                new Intent(TelecomManager.ACTION_SHOW_RESPOND_VIA_SMS_SETTINGS);
-        quickResponseSettingsHeader.titleRes = R.string.respond_via_sms_setting_title;
-        quickResponseSettingsHeader.intent = quickResponseSettingsIntent;
-        target.add(quickResponseSettingsHeader);
+        if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M)
+                >= Build.VERSION_CODES.M) {
+            Header quickResponseSettingsHeader = new Header();
+            Intent quickResponseSettingsIntent =
+                    new Intent(TelecomManager.ACTION_SHOW_RESPOND_VIA_SMS_SETTINGS);
+            quickResponseSettingsHeader.titleRes = R.string.respond_via_sms_setting_title;
+            quickResponseSettingsHeader.intent = quickResponseSettingsIntent;
+            target.add(quickResponseSettingsHeader);
+        }
+
 
         TelephonyManager telephonyManager =
                 (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
 
-        // Only show call setting menus if the current user is the primary/owner user.
-        if (isPrimaryUser()) {
-            // Show "Call Settings" if there is one SIM and "Phone Accounts" if there are more.
-            if (telephonyManager.getPhoneCount() <= 1) {
-                Header callSettingsHeader = new Header();
-                Intent callSettingsIntent = new Intent(TelecomManager.ACTION_SHOW_CALL_SETTINGS);
-                callSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        // "Call Settings" (full settings) is shown if the current user is primary user and there 
+        // is only one SIM. Before N, "Calling accounts" setting is shown if the current user is 
+        // primary user and there are multiple SIMs. In N+, "Calling accounts" is shown whenever
+        // "Call Settings" is not shown.
+        boolean isPrimaryUser = isPrimaryUser();
+        if (isPrimaryUser && telephonyManager.getPhoneCount() <= 1) {
+            Header callSettingsHeader = new Header();
+            Intent callSettingsIntent = new Intent(TelecomManager.ACTION_SHOW_CALL_SETTINGS);
+            callSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
 
-                callSettingsHeader.titleRes = R.string.call_settings_label;
-                callSettingsHeader.intent = callSettingsIntent;
-                target.add(callSettingsHeader);
-            } else {
-                Header phoneAccountSettingsHeader = new Header();
-                Intent phoneAccountSettingsIntent =
-                        new Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS);
-                phoneAccountSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            callSettingsHeader.titleRes = R.string.call_settings_label;
+            callSettingsHeader.intent = callSettingsIntent;
+            target.add(callSettingsHeader);
+        } else if (android.os.Build.VERSION.CODENAME.startsWith("N") || isPrimaryUser) {
+            Header phoneAccountSettingsHeader = new Header();
+            Intent phoneAccountSettingsIntent =
+                    new Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS);
+            phoneAccountSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
 
-                phoneAccountSettingsHeader.titleRes = R.string.phone_account_settings_label;
-                phoneAccountSettingsHeader.intent = phoneAccountSettingsIntent;
-                target.add(phoneAccountSettingsHeader);
-            }
-
+            phoneAccountSettingsHeader.titleRes = R.string.phone_account_settings_label;
+            phoneAccountSettingsHeader.intent = phoneAccountSettingsIntent;
+            target.add(phoneAccountSettingsHeader);
+        }
+        if (isPrimaryUser) {
             Header blockedCallsHeader = new Header();
             blockedCallsHeader.titleRes = R.string.manage_blocked_numbers_label;
             blockedCallsHeader.intent = new Intent(this, BlockedNumbersSettingsActivity.class);
             target.add(blockedCallsHeader);
 
-            if (telephonyManager.isTtyModeSupported()
-                    || telephonyManager.isHearingAidCompatibilitySupported()) {
+            if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M)
+                    >= Build.VERSION_CODES.M
+                    && (telephonyManager.isTtyModeSupported()
+                    || telephonyManager.isHearingAidCompatibilitySupported())) {
                 Header accessibilitySettingsHeader = new Header();
                 Intent accessibilitySettingsIntent =
                         new Intent(TelecomManager.ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS);
@@ -109,7 +120,7 @@
             // If we don't have the permission to write to system settings, go to system sound
             // settings instead. Otherwise, perform the super implementation (which launches our
             // own preference fragment.
-            if (!Settings.System.canWrite(this)) {
+            if (!SettingsCompat.System.canWrite(this)) {
                 Toast.makeText(
                         this,
                         getResources().getString(R.string.toast_cannot_write_system_settings),
diff --git a/src/com/android/dialer/settings/SoundSettingsFragment.java b/src/com/android/dialer/settings/SoundSettingsFragment.java
index 35e1dae..59f8798 100644
--- a/src/com/android/dialer/settings/SoundSettingsFragment.java
+++ b/src/com/android/dialer/settings/SoundSettingsFragment.java
@@ -16,10 +16,9 @@
 
 package com.android.dialer.settings;
 
-import android.app.AppOpsManager;
 import android.content.Context;
-import android.content.Intent;
 import android.media.RingtoneManager;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -32,21 +31,13 @@
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
 import android.telephony.TelephonyManager;
-import android.view.MenuItem;
 import android.widget.Toast;
 
-import com.android.contacts.common.util.PermissionsUtil;
+import com.android.contacts.common.compat.SdkVersionOverride;
 import com.android.dialer.R;
+import com.android.dialer.compat.SettingsCompat;
 import com.android.phone.common.util.SettingsUtil;
 
-import java.lang.Boolean;
-import java.lang.CharSequence;
-import java.lang.Object;
-import java.lang.Override;
-import java.lang.Runnable;
-import java.lang.String;
-import java.lang.Thread;
-
 public class SoundSettingsFragment extends PreferenceFragment
         implements Preference.OnPreferenceChangeListener {
 
@@ -120,7 +111,8 @@
 
         TelephonyManager telephonyManager =
                 (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
-        if (telephonyManager.canChangeDtmfToneLength()
+        if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M) >= Build.VERSION_CODES.M
+                && telephonyManager.canChangeDtmfToneLength()
                 && (telephonyManager.isWorldPhone() || !shouldHideCarrierSettings())) {
             mDtmfToneLength.setOnPreferenceChangeListener(this);
             mDtmfToneLength.setValueIndex(
@@ -137,7 +129,7 @@
     public void onResume() {
         super.onResume();
 
-        if (!Settings.System.canWrite(getContext())) {
+        if (!SettingsCompat.System.canWrite(getContext())) {
             // If the user launches this setting fragment, then toggles the WRITE_SYSTEM_SETTINGS
             // AppOp, then close the fragment since there is nothing useful to do.
             getActivity().onBackPressed();
@@ -160,7 +152,7 @@
      */
     @Override
     public boolean onPreferenceChange(Preference preference, Object objValue) {
-        if (!Settings.System.canWrite(getContext())) {
+        if (!SettingsCompat.System.canWrite(getContext())) {
             // A user shouldn't be able to get here, but this protects against monkey crashes.
             Toast.makeText(
                     getContext(),
@@ -186,7 +178,7 @@
      */
     @Override
     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
-        if (!Settings.System.canWrite(getContext())) {
+        if (!SettingsCompat.System.canWrite(getContext())) {
             Toast.makeText(
                     getContext(),
                     getResources().getString(R.string.toast_cannot_write_system_settings),
diff --git a/src/com/android/dialer/util/TelecomUtil.java b/src/com/android/dialer/util/TelecomUtil.java
index 58675cf..a0c8334 100644
--- a/src/com/android/dialer/util/TelecomUtil.java
+++ b/src/com/android/dialer/util/TelecomUtil.java
@@ -22,11 +22,14 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.CallLog.Calls;
+import android.support.v4.content.ContextCompat;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.dialer.compat.DialerCompatUtils;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -181,11 +184,15 @@
     }
 
     private static boolean hasPermission(Context context, String permission) {
-        return context.getPackageManager().checkPermission(permission, context.getPackageName())
+        return ContextCompat.checkSelfPermission(context, permission)
                 == PackageManager.PERMISSION_GRANTED;
     }
 
     public static boolean isDefaultDialer(Context context) {
+        if (!DialerCompatUtils.isDefaultDialerCompatible()) {
+            return false;
+        }
+
         final boolean result = TextUtils.equals(context.getPackageName(),
                 getTelecomManager(context).getDefaultDialerPackage());
         if (result) {
diff --git a/src/com/android/dialer/voicemail/VoicemailAudioManager.java b/src/com/android/dialer/voicemail/VoicemailAudioManager.java
index 267eeca..712b20b 100644
--- a/src/com/android/dialer/voicemail/VoicemailAudioManager.java
+++ b/src/com/android/dialer/voicemail/VoicemailAudioManager.java
@@ -19,10 +19,10 @@
 import android.content.Context;
 import android.media.AudioManager;
 import android.media.AudioManager.OnAudioFocusChangeListener;
-import android.telecom.CallAudioState;
 import android.util.Log;
 
-import java.util.Objects;
+import com.android.dialer.compat.CallAudioStateCompat;
+
 import java.util.concurrent.RejectedExecutionException;
 
 /**
@@ -38,7 +38,7 @@
     private VoicemailPlaybackPresenter mVoicemailPlaybackPresenter;
     private WiredHeadsetManager mWiredHeadsetManager;
     private boolean mWasSpeakerOn;
-    private CallAudioState mCallAudioState;
+    private CallAudioStateCompat mCallAudioState;
 
     public VoicemailAudioManager(Context context,
             VoicemailPlaybackPresenter voicemailPlaybackPresenter) {
@@ -82,25 +82,26 @@
 
         int newRoute = mCallAudioState.getRoute();  // start out with existing route
         if (newIsPluggedIn) {
-            newRoute = CallAudioState.ROUTE_WIRED_HEADSET;
+            newRoute = CallAudioStateCompat.ROUTE_WIRED_HEADSET;
         } else {
             if (mWasSpeakerOn) {
-                newRoute = CallAudioState.ROUTE_SPEAKER;
+                newRoute = CallAudioStateCompat.ROUTE_SPEAKER;
             } else {
-                newRoute = CallAudioState.ROUTE_EARPIECE;
+                newRoute = CallAudioStateCompat.ROUTE_EARPIECE;
             }
         }
 
-        mVoicemailPlaybackPresenter.setSpeakerphoneOn(newRoute == CallAudioState.ROUTE_SPEAKER);
+        mVoicemailPlaybackPresenter.setSpeakerphoneOn(newRoute == CallAudioStateCompat.ROUTE_SPEAKER);
 
         // We need to call this every time even if we do not change the route because the supported
         // routes changed either to include or not include WIRED_HEADSET.
         setSystemAudioState(
-                new CallAudioState(false /* muted */, newRoute, calculateSupportedRoutes()));
+                new CallAudioStateCompat(false /* muted */, newRoute, calculateSupportedRoutes()));
     }
 
     public void setSpeakerphoneOn(boolean on) {
-        setAudioRoute(on ? CallAudioState.ROUTE_SPEAKER : CallAudioState.ROUTE_WIRED_OR_EARPIECE);
+        setAudioRoute(on ? CallAudioStateCompat.ROUTE_SPEAKER
+                : CallAudioStateCompat.ROUTE_WIRED_OR_EARPIECE);
     }
 
     public boolean isWiredHeadsetPluggedIn() {
@@ -119,10 +120,10 @@
     /**
      * Change the audio route, for example from earpiece to speakerphone.
      *
-     * @param route The new audio route to use. See {@link CallAudioState}.
+     * @param route The new audio route to use. See {@link CallAudioStateCompat}.
      */
     void setAudioRoute(int route) {
-        Log.v(TAG, "setAudioRoute, route: " + CallAudioState.audioRouteToString(route));
+        Log.v(TAG, "setAudioRoute, route: " + CallAudioStateCompat.audioRouteToString(route));
 
         // Change ROUTE_WIRED_OR_EARPIECE to a single entry.
         int newRoute = selectWiredOrEarpiece(route, mCallAudioState.getSupportedRouteMask());
@@ -136,25 +137,25 @@
         if (mCallAudioState.getRoute() != newRoute) {
             // Remember the new speaker state so it can be restored when the user plugs and unplugs
             // a headset.
-            mWasSpeakerOn = newRoute == CallAudioState.ROUTE_SPEAKER;
-            setSystemAudioState(new CallAudioState(false /* muted */, newRoute,
+            mWasSpeakerOn = newRoute == CallAudioStateCompat.ROUTE_SPEAKER;
+            setSystemAudioState(new CallAudioStateCompat(false /* muted */, newRoute,
                     mCallAudioState.getSupportedRouteMask()));
         }
     }
 
-    private CallAudioState getInitialAudioState() {
+    private CallAudioStateCompat getInitialAudioState() {
         int supportedRouteMask = calculateSupportedRoutes();
-        int route = selectWiredOrEarpiece(CallAudioState.ROUTE_WIRED_OR_EARPIECE,
+        int route = selectWiredOrEarpiece(CallAudioStateCompat.ROUTE_WIRED_OR_EARPIECE,
                 supportedRouteMask);
-        return new CallAudioState(false /* muted */, route, supportedRouteMask);
+        return new CallAudioStateCompat(false /* muted */, route, supportedRouteMask);
     }
 
     private int calculateSupportedRoutes() {
-        int routeMask = CallAudioState.ROUTE_SPEAKER;
+        int routeMask = CallAudioStateCompat.ROUTE_SPEAKER;
         if (mWiredHeadsetManager.isPluggedIn()) {
-            routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
+            routeMask |= CallAudioStateCompat.ROUTE_WIRED_HEADSET;
         } else {
-            routeMask |= CallAudioState.ROUTE_EARPIECE;
+            routeMask |= CallAudioStateCompat.ROUTE_EARPIECE;
         }
         return routeMask;
     }
@@ -163,29 +164,29 @@
         // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of
         // ROUTE_WIRED_OR_EARPIECE so that callers don't have to make a call to check which is
         // supported before calling setAudioRoute.
-        if (route == CallAudioState.ROUTE_WIRED_OR_EARPIECE) {
-            route = CallAudioState.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask;
+        if (route == CallAudioStateCompat.ROUTE_WIRED_OR_EARPIECE) {
+            route = CallAudioStateCompat.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask;
             if (route == 0) {
                 Log.wtf(TAG, "One of wired headset or earpiece should always be valid.");
                 // assume earpiece in this case.
-                route = CallAudioState.ROUTE_EARPIECE;
+                route = CallAudioStateCompat.ROUTE_EARPIECE;
             }
         }
         return route;
     }
 
-    private void setSystemAudioState(CallAudioState callAudioState) {
-        CallAudioState oldAudioState = mCallAudioState;
+    private void setSystemAudioState(CallAudioStateCompat callAudioState) {
+        CallAudioStateCompat oldAudioState = mCallAudioState;
         mCallAudioState = callAudioState;
 
         Log.i(TAG, "setSystemAudioState: changing from " + oldAudioState + " to "
                 + mCallAudioState);
 
         // Audio route.
-        if (mCallAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
+        if (mCallAudioState.getRoute() == CallAudioStateCompat.ROUTE_SPEAKER) {
             turnOnSpeaker(true);
-        } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_EARPIECE ||
-                mCallAudioState.getRoute() == CallAudioState.ROUTE_WIRED_HEADSET) {
+        } else if (mCallAudioState.getRoute() == CallAudioStateCompat.ROUTE_EARPIECE ||
+                mCallAudioState.getRoute() == CallAudioStateCompat.ROUTE_WIRED_HEADSET) {
             // Just handle turning off the speaker, the system will handle switching between wired
             // headset and earpiece.
             turnOnSpeaker(false);
diff --git a/tests/src/com/android/dialer/calllog/CallLogAdapterTest.java b/tests/src/com/android/dialer/calllog/CallLogAdapterTest.java
index 12ae785..80dfe35 100644
--- a/tests/src/com/android/dialer/calllog/CallLogAdapterTest.java
+++ b/tests/src/com/android/dialer/calllog/CallLogAdapterTest.java
@@ -240,7 +240,9 @@
         mAdapter.changeCursor(mCursor);
         mAdapter.onBindViewHolder(mViewHolder, 0);
 
-        assertHasCallActionToGivenNumber(mViewHolder, TEST_NUMBER + TEST_POST_DIAL_DIGITS);
+        if (PhoneNumberDisplayUtil.canShowPostDial()) {
+            assertHasCallActionToGivenNumber(mViewHolder, TEST_NUMBER + TEST_POST_DIAL_DIGITS);
+        }
     }
 
     @MediumTest
@@ -262,7 +264,9 @@
         mAdapter.changeCursor(mCursor);
         mAdapter.onBindViewHolder(mViewHolder, 0);
 
-        assertNameIs(mViewHolder, TEST_NUMBER + TEST_POST_DIAL_DIGITS);
+        if (PhoneNumberDisplayUtil.canShowPostDial()) {
+            assertNameIs(mViewHolder, TEST_NUMBER + TEST_POST_DIAL_DIGITS);
+        }
     }
 
     @MediumTest
@@ -274,7 +278,9 @@
         mAdapter.changeCursor(mCursor);
         mAdapter.onBindViewHolder(mViewHolder, 0);
 
-        assertNameIs(mViewHolder, TEST_CACHED_NAME_PRIMARY);
+        if (PhoneNumberDisplayUtil.canShowPostDial()) {
+            assertNameIs(mViewHolder, TEST_CACHED_NAME_PRIMARY);
+        }
     }
 
     @MediumTest
@@ -655,7 +661,7 @@
         if (!TextUtils.isEmpty(number)) {
             values[CallLogQuery.NUMBER] = number;
         }
-        if (!TextUtils.isEmpty(postDialDigits)) {
+        if (!TextUtils.isEmpty(postDialDigits) && PhoneNumberDisplayUtil.canShowPostDial()) {
             values[CallLogQuery.POST_DIAL_DIGITS] = postDialDigits;
         }
         if (presentation != NO_VALUE_SET) {
diff --git a/tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java b/tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java
index fa18aff..04463c2 100644
--- a/tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java
+++ b/tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java
@@ -98,9 +98,14 @@
 
         mBuilder.addGroups(mCursor);
 
-        assertEquals(2, mFakeGroupCreator.groups.size());
-        assertGroupIs(0, 2, mFakeGroupCreator.groups.get(0));
-        assertGroupIs(2, 1, mFakeGroupCreator.groups.get(1));
+        if (PhoneNumberDisplayUtil.canShowPostDial()) {
+            assertEquals(2, mFakeGroupCreator.groups.size());
+            assertGroupIs(0, 2, mFakeGroupCreator.groups.get(0));
+            assertGroupIs(2, 1, mFakeGroupCreator.groups.get(1));
+        } else {
+            assertEquals(1, mFakeGroupCreator.groups.size());
+            assertGroupIs(0, 3, mFakeGroupCreator.groups.get(0));
+        }
     }
 
     public void testAddGroups_MatchingIncomingAndOutgoing() {
@@ -339,7 +344,9 @@
         values[CallLogQuery.ID] = mCursor.getPosition();
         values[CallLogQuery.NUMBER] = number;
         values[CallLogQuery.CALL_TYPE] = type;
-        values[CallLogQuery.POST_DIAL_DIGITS] = postDialDigits;
+        if (PhoneNumberDisplayUtil.canShowPostDial()) {
+            values[CallLogQuery.POST_DIAL_DIGITS] = postDialDigits;
+        }
         mCursor.addRow(values);
     }
 
diff --git a/tests/src/com/android/dialer/calllog/TestTelecomCallLogCache.java b/tests/src/com/android/dialer/calllog/TestTelecomCallLogCache.java
index 5475ec3..fcf9884 100644
--- a/tests/src/com/android/dialer/calllog/TestTelecomCallLogCache.java
+++ b/tests/src/com/android/dialer/calllog/TestTelecomCallLogCache.java
@@ -17,13 +17,20 @@
 package com.android.dialer.calllog;
 
 import android.content.Context;
+import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 
+import com.android.dialer.calllog.calllogcache.CallLogCache;
+
 /**
- * Modified version of {@link com.android.dialer.calllog.PhoneNumberDisplayHelper} to be used in
+ * Modified version of {@link com.android.dialer.calllog.calllogcache.CallLogCache} to be used in
  * tests that allows injecting the voicemail number.
+ *
+ * NOTE: This tests the pre-LMR1 version because currently none of the tests involve multi-SIM,
+ * but...
+ * TODO: write tests to test multi-SIM functionality in TelecomCallLogCache.
  */
-public final class TestTelecomCallLogCache extends TelecomCallLogCache {
+public final class TestTelecomCallLogCache extends CallLogCache {
     private CharSequence mVoicemailNumber;
 
     public TestTelecomCallLogCache(Context context, CharSequence voicemailNumber) {
@@ -35,4 +42,19 @@
     public boolean isVoicemailNumber(PhoneAccountHandle accountHandle, CharSequence number) {
         return mVoicemailNumber.equals(number);
     }
+
+    @Override
+    public String getAccountLabel(PhoneAccountHandle accountHandle) {
+        return null;
+    }
+
+    @Override
+    public int getAccountColor(PhoneAccountHandle accountHandle) {
+        return PhoneAccount.NO_HIGHLIGHT_COLOR;
+    }
+
+    @Override
+    public boolean doesAccountSupportCallSubject(PhoneAccountHandle accountHandle) {
+        return false;
+    }
 }
diff --git a/tests/src/com/android/dialer/filterednumber/FilteredNumbersUtilTest.java b/tests/src/com/android/dialer/filterednumber/FilteredNumbersUtilTest.java
index 180295c..ccd95ab 100644
--- a/tests/src/com/android/dialer/filterednumber/FilteredNumbersUtilTest.java
+++ b/tests/src/com/android/dialer/filterednumber/FilteredNumbersUtilTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.dialer.filterednumber;
 
+import android.preference.PreferenceManager;
 import android.test.AndroidTestCase;
 
 import com.android.contacts.common.test.mocks.ContactsMockContext;
@@ -46,6 +47,12 @@
     public void setUp() throws Exception {
         super.setUp();
         mContext = new ContactsMockContext(getContext(), FilteredNumberContract.AUTHORITY);
+
+        // Reset whether an emergency number was dialed
+        PreferenceManager.getDefaultSharedPreferences(mContext)
+                .edit()
+                .putLong(FilteredNumbersUtil.LAST_EMERGENCY_CALL_MS_PREF_KEY, 0)
+                .apply();
     }
 
     public void testShouldBlockVoicemail_NotBlocked() {
@@ -78,6 +85,17 @@
                 COUNTRY_ISO, EARLIER_TIME + 30000));
     }
 
+    public void testShouldBlockVoicemail_AfterEmergencyCall() {
+        // Just called emergency services
+        PreferenceManager.getDefaultSharedPreferences(mContext)
+                .edit()
+                .putLong(FilteredNumbersUtil.LAST_EMERGENCY_CALL_MS_PREF_KEY,
+                        System.currentTimeMillis())
+                .apply();
+        assertFalse(FilteredNumbersUtil.shouldBlockVoicemail(mContext, NORMALIZED_NUMBER,
+                COUNTRY_ISO, 0));
+    }
+
     private void setupShouldBlockVoicemailQuery(long creationTimeMs) {
         Query query = mContext.getContactsProvider().expectQuery(FilteredNumber.CONTENT_URI)
                 .withProjection(FILTERED_NUMBER_PROJECTION)