Merge "Dismiss dialpad when clicking in search area." into ub-contactsdialer-b-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 91c91b9..67161ad 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -294,7 +294,8 @@
                   android:launchMode="singleInstance"
                   android:configChanges="keyboardHidden"
                   android:exported="false"
-                  android:screenOrientation="nosensor" >
+                  android:screenOrientation="nosensor"
+                  android:encryptionAware="true" >
         </activity>
 
         <!-- BroadcastReceiver for receiving Intents from Notification mechanism. -->
@@ -302,7 +303,8 @@
                   android:exported="false" />
 
         <service android:name="com.android.incallui.InCallServiceImpl"
-                 android:permission="android.permission.BIND_INCALL_SERVICE" >
+                 android:permission="android.permission.BIND_INCALL_SERVICE"
+                 android:encryptionAware="true" >
             <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
diff --git a/res/layout/call_log_list_item_actions.xml b/res/layout/call_log_list_item_actions.xml
index ca00405..63385ee 100644
--- a/res/layout/call_log_list_item_actions.xml
+++ b/res/layout/call_log_list_item_actions.xml
@@ -129,6 +129,11 @@
 
     </LinearLayout>
 
+    <ViewStub
+        android:id="@+id/spam_actions_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
+
     <LinearLayout
         android:id="@+id/details_action"
         style="@style/CallLogActionStyle">
diff --git a/src-N/com/android/dialer/compat/UserManagerSdkCompat.java b/src-N/com/android/dialer/compat/UserManagerSdkCompat.java
new file mode 100644
index 0000000..9a08d4e
--- /dev/null
+++ b/src-N/com/android/dialer/compat/UserManagerSdkCompat.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * UserManagerCompat respecting Sdk requirements
+ */
+public class UserManagerSdkCompat {
+
+    /**
+     * Return whether the calling user is running in an "unlocked" state. A user
+     * is unlocked only after they've entered their credentials (such as a lock
+     * pattern or PIN), and credential-encrypted private app data storage is
+     * available.
+     *
+     * @param context the current context
+     * @return {@code true} if the user is unlocked or context is null, {@code false} otherwise
+     * @throws NullPointerException if context is null
+     */
+    public static boolean isUserUnlocked(Context context) {
+        return android.support.v4.os.UserManagerCompat.isUserUnlocked(context);
+    }
+
+}
diff --git a/src-pre-N/com/android/dialer/compat/UserManagerSdkCompat.java b/src-pre-N/com/android/dialer/compat/UserManagerSdkCompat.java
new file mode 100644
index 0000000..c79ac2f
--- /dev/null
+++ b/src-pre-N/com/android/dialer/compat/UserManagerSdkCompat.java
@@ -0,0 +1,34 @@
+/*
+ * 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.util.Log;
+
+/**
+ * UserManagerCompat respecting Sdk requirements
+ */
+public class UserManagerSdkCompat {
+
+    /**
+     * @return {@code true}
+     */
+    public static boolean isUserUnlocked(Context context) {
+        Log.wtf("UserManagerSdkCompat", "Not implemented");
+        return true;
+    }
+
+}
diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java
index c045967..56e29a9 100644
--- a/src/com/android/dialer/CallDetailActivity.java
+++ b/src/com/android/dialer/CallDetailActivity.java
@@ -153,6 +153,8 @@
             final boolean canPlaceCallsTo =
                     PhoneNumberUtil.canPlaceCallsTo(mNumber, mDetails.numberPresentation);
             mCallButton.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE);
+            mCopyNumberActionItem.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE);
+            mBlockNumberActionItem.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE);
 
             final boolean isSipNumber = PhoneNumberUtil.isSipNumber(mNumber);
             final boolean isVoicemailNumber =
@@ -221,6 +223,7 @@
     private TextView mBlockNumberActionItem;
     private View mEditBeforeCallActionItem;
     private View mReportActionItem;
+    private View mCopyNumberActionItem;
 
     private Integer mBlockedNumberId;
 
@@ -279,8 +282,8 @@
         mReportActionItem = findViewById(R.id.call_detail_action_report);
         mReportActionItem.setOnClickListener(this);
 
-        View copyActionItem = findViewById(R.id.call_detail_action_copy);
-        copyActionItem.setOnClickListener(this);
+        mCopyNumberActionItem = findViewById(R.id.call_detail_action_copy);
+        mCopyNumberActionItem.setOnClickListener(this);
 
         if (getIntent().getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) {
             closeSystemDialogs();
diff --git a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
index 7e14719..f76312d 100644
--- a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
+++ b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
@@ -55,10 +55,16 @@
 import com.android.dialer.filterednumber.FilteredNumbersUtil;
 import com.android.dialer.logging.Logger;
 import com.android.dialer.logging.ScreenEvent;
+import com.android.dialer.service.SpamButtonRenderer;
 import com.android.dialer.util.DialerUtils;
 import com.android.dialer.util.PhoneNumberUtil;
 import com.android.dialer.voicemail.VoicemailPlaybackLayout;
 import com.android.dialer.voicemail.VoicemailPlaybackPresenter;
+import com.android.dialerbind.ObjectFactory;
+import com.google.common.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * This is an object containing references to views contained by the call log list item. This
@@ -199,6 +205,9 @@
 
     private final int mPhotoSize;
 
+    private ViewStub mSpamViewStub;
+    private SpamButtonRenderer mSpamButtonRenderer;
+
     private View.OnClickListener mExpandCollapseListener;
     private boolean mVoicemailPrimaryActionButtonClicked;
 
@@ -403,6 +412,9 @@
 
             callWithNoteButtonView = actionsView.findViewById(R.id.call_with_note_action);
             callWithNoteButtonView.setOnClickListener(this);
+
+            mSpamViewStub = (ViewStub) actionsView.findViewById(R.id.spam_actions_container);
+            mSpamButtonRenderer = ObjectFactory.newSpamButtonRenderer(mContext, mSpamViewStub);
         }
 
         bindActionButtons();
@@ -501,7 +513,7 @@
         } else {
             detailsButtonView.setVisibility(View.VISIBLE);
             detailsButtonView.setTag(
-                IntentProvider.getCallDetailIntentProvider(rowId, callIds, null));
+                    IntentProvider.getCallDetailIntentProvider(rowId, callIds, null));
         }
 
         if (info != null && UriUtils.isEncodedContactUri(info.lookupUri)) {
@@ -532,6 +544,24 @@
                 mCallLogCache.isVoicemailNumber(accountHandle, number);
         callWithNoteButtonView.setVisibility(
                 supportsCallSubject && !isVoicemailNumber ? View.VISIBLE : View.GONE);
+
+        if(mSpamButtonRenderer != null){
+            List<View> completeLogListItems = Lists.newArrayList(
+                    createNewContactButtonView,
+                    addToExistingContactButtonView,
+                    sendMessageView,
+                    callButtonView,
+                    callWithNoteButtonView,
+                    detailsButtonView,
+                    voicemailPlaybackView);
+            List<View> blockedNumberVisibleViews = new ArrayList<>();
+            List<View> spamNumberVisibleViews = Lists.newArrayList(detailsButtonView);
+
+            mSpamButtonRenderer.setCompleteListItemViews(completeLogListItems);
+            mSpamButtonRenderer.setFilteredNumberViews(blockedNumberVisibleViews);
+            mSpamButtonRenderer.setSpamFilteredViews(spamNumberVisibleViews);
+            mSpamButtonRenderer.render(number, countryIso);
+        }
     }
 
     /**
diff --git a/src/com/android/dialer/calllog/CallLogNotificationsHelper.java b/src/com/android/dialer/calllog/CallLogNotificationsHelper.java
index 367cb78..91012b5 100644
--- a/src/com/android/dialer/calllog/CallLogNotificationsHelper.java
+++ b/src/com/android/dialer/calllog/CallLogNotificationsHelper.java
@@ -16,14 +16,94 @@
 
 package com.android.dialer.calllog;
 
-import android.content.Context;
+import static android.Manifest.permission.READ_CALL_LOG;
+import static android.Manifest.permission.READ_CONTACTS;
 
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract.PhoneLookup;
+import android.support.annotation.Nullable;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.contacts.common.util.PermissionsUtil;
+import com.android.dialer.R;
 import com.android.dialer.util.TelecomUtil;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Helper class operating on call log notifications.
  */
 public class CallLogNotificationsHelper {
+    private static final String TAG = "CallLogNotifHelper";
+    private static CallLogNotificationsHelper sInstance;
+
+    /** Returns the singleton instance of the {@link CallLogNotificationsHelper}. */
+    public static CallLogNotificationsHelper getInstance(Context context) {
+        if (sInstance == null) {
+            ContentResolver contentResolver = context.getContentResolver();
+            sInstance = new CallLogNotificationsHelper(context,
+                    createNewCallsQuery(context, contentResolver),
+                    createNameLookupQuery(context, contentResolver));
+        }
+        return sInstance;
+    }
+
+    private final Context mContext;
+    private final NewCallsQuery mNewCallsQuery;
+    private final NameLookupQuery mNameLookupQuery;
+
+    CallLogNotificationsHelper(Context context, NewCallsQuery newCallsQuery,
+            NameLookupQuery nameLookupQuery) {
+        mContext = context;
+        mNewCallsQuery = newCallsQuery;
+        mNameLookupQuery = nameLookupQuery;
+    }
+
+    /**
+     * Get all voicemails with the "new" flag set to 1.
+     *
+     * @return A list of NewCall objects where each object represents a new voicemail.
+     */
+    @Nullable
+    public List<NewCall> getNewVoicemails() {
+        return mNewCallsQuery.query(Calls.VOICEMAIL_TYPE);
+    }
+
+    /**
+     * Given a number and number information (presentation and country ISO), get the best name
+     * for display. If the name itself if already available, return that. Otherwise attempt to look
+     * it up in the database. If that fails, fall back to displaying the number.
+     */
+    public String getName(@Nullable String number, int numberPresentation,
+                          @Nullable String countryIso) {
+        String name = PhoneNumberDisplayUtil.getDisplayName(
+                mContext,
+                number,
+                numberPresentation,
+                false).toString();
+        if (!TextUtils.isEmpty(name)) {
+            return name;
+        }
+        // Look it up in the database.
+        name = mNameLookupQuery.query(number);
+        if (!TextUtils.isEmpty(name)) {
+            return name;
+        }
+        if (!TextUtils.isEmpty(number)) {
+            // If we cannot lookup the contact, use the number instead.
+            return PhoneNumberUtils.formatNumber(number, countryIso);
+        }
+        return mContext.getResources().getString(R.string.unknown);
+    }
+
     /** Removes the missed call notifications. */
     public static void removeMissedCallNotifications(Context context) {
         TelecomUtil.cancelMissedCallsNotification(context);
@@ -33,4 +113,188 @@
     public static void updateVoicemailNotifications(Context context) {
         CallLogNotificationsService.updateVoicemailNotifications(context, null);
     }
+
+    /** Information about a new voicemail. */
+    public static final class NewCall {
+        public final Uri callsUri;
+        public final Uri voicemailUri;
+        public final String number;
+        public final int numberPresentation;
+        public final String accountComponentName;
+        public final String accountId;
+        public final String transcription;
+        public final String countryIso;
+        public final long dateMs;
+
+        public NewCall(
+                Uri callsUri,
+                Uri voicemailUri,
+                String number,
+                int numberPresentation,
+                String accountComponentName,
+                String accountId,
+                String transcription,
+                String countryIso,
+                long dateMs) {
+            this.callsUri = callsUri;
+            this.voicemailUri = voicemailUri;
+            this.number = number;
+            this.numberPresentation = numberPresentation;
+            this.accountComponentName = accountComponentName;
+            this.accountId = accountId;
+            this.transcription = transcription;
+            this.countryIso = countryIso;
+            this.dateMs = dateMs;
+        }
+    }
+
+    /** Allows determining the new calls for which a notification should be generated. */
+    public interface NewCallsQuery {
+        /**
+         * Returns the new calls of a certain type for which a notification should be generated.
+         */
+        @Nullable
+        public List<NewCall> query(int type);
+    }
+
+    /** Create a new instance of {@link NewCallsQuery}. */
+    public static NewCallsQuery createNewCallsQuery(Context context,
+            ContentResolver contentResolver) {
+
+        return new DefaultNewCallsQuery(context.getApplicationContext(), contentResolver);
+    }
+
+    /**
+     * Default implementation of {@link NewCallsQuery} that looks up the list of new calls to
+     * notify about in the call log.
+     */
+    private static final class DefaultNewCallsQuery implements NewCallsQuery {
+        private static final String[] PROJECTION = {
+            Calls._ID,
+            Calls.NUMBER,
+            Calls.VOICEMAIL_URI,
+            Calls.NUMBER_PRESENTATION,
+            Calls.PHONE_ACCOUNT_COMPONENT_NAME,
+            Calls.PHONE_ACCOUNT_ID,
+            Calls.TRANSCRIPTION,
+            Calls.COUNTRY_ISO,
+            Calls.DATE
+        };
+        private static final int ID_COLUMN_INDEX = 0;
+        private static final int NUMBER_COLUMN_INDEX = 1;
+        private static final int VOICEMAIL_URI_COLUMN_INDEX = 2;
+        private static final int NUMBER_PRESENTATION_COLUMN_INDEX = 3;
+        private static final int PHONE_ACCOUNT_COMPONENT_NAME_COLUMN_INDEX = 4;
+        private static final int PHONE_ACCOUNT_ID_COLUMN_INDEX = 5;
+        private static final int TRANSCRIPTION_COLUMN_INDEX = 6;
+        private static final int COUNTRY_ISO_COLUMN_INDEX = 7;
+        private static final int DATE_COLUMN_INDEX = 8;
+
+        private final ContentResolver mContentResolver;
+        private final Context mContext;
+
+        private DefaultNewCallsQuery(Context context, ContentResolver contentResolver) {
+            mContext = context;
+            mContentResolver = contentResolver;
+        }
+
+        @Override
+        @Nullable
+        public List<NewCall> query(int type) {
+            if (!PermissionsUtil.hasPermission(mContext, READ_CALL_LOG)) {
+                Log.w(TAG, "No READ_CALL_LOG permission, returning null for calls lookup.");
+                return null;
+            }
+            final String selection = String.format("%s = 1 AND %s = ?", Calls.NEW, Calls.TYPE);
+            final String[] selectionArgs = new String[]{ Integer.toString(type) };
+            try (Cursor cursor = mContentResolver.query(Calls.CONTENT_URI_WITH_VOICEMAIL,
+                    PROJECTION, selection, selectionArgs, Calls.DEFAULT_SORT_ORDER)) {
+                if (cursor == null) {
+                    return null;
+                }
+                List<NewCall> newCalls = new ArrayList<>();
+                while (cursor.moveToNext()) {
+                    newCalls.add(createNewCallsFromCursor(cursor));
+                }
+                return newCalls;
+            } catch (RuntimeException e) {
+                Log.w(TAG, "Exception when querying Contacts Provider for calls lookup");
+                return null;
+            }
+        }
+
+        /** Returns an instance of {@link NewCall} created by using the values of the cursor. */
+        private NewCall createNewCallsFromCursor(Cursor cursor) {
+            String voicemailUriString = cursor.getString(VOICEMAIL_URI_COLUMN_INDEX);
+            Uri callsUri = ContentUris.withAppendedId(
+                    Calls.CONTENT_URI_WITH_VOICEMAIL, cursor.getLong(ID_COLUMN_INDEX));
+            Uri voicemailUri = voicemailUriString == null ? null : Uri.parse(voicemailUriString);
+            return new NewCall(
+                    callsUri,
+                    voicemailUri,
+                    cursor.getString(NUMBER_COLUMN_INDEX),
+                    cursor.getInt(NUMBER_PRESENTATION_COLUMN_INDEX),
+                    cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME_COLUMN_INDEX),
+                    cursor.getString(PHONE_ACCOUNT_ID_COLUMN_INDEX),
+                    cursor.getString(TRANSCRIPTION_COLUMN_INDEX),
+                    cursor.getString(COUNTRY_ISO_COLUMN_INDEX),
+                    cursor.getLong(DATE_COLUMN_INDEX));
+        }
+    }
+
+    /** Allows determining the name associated with a given phone number. */
+    public interface NameLookupQuery {
+        /**
+         * Returns the name associated with the given number in the contacts database, or null if
+         * the number does not correspond to any of the contacts.
+         * <p>
+         * If there are multiple contacts with the same phone number, it will return the name of one
+         * of the matching contacts.
+         */
+        @Nullable
+        public String query(@Nullable String number);
+    }
+
+    /** Create a new instance of {@link NameLookupQuery}. */
+    public static NameLookupQuery createNameLookupQuery(Context context,
+            ContentResolver contentResolver) {
+        return new DefaultNameLookupQuery(context.getApplicationContext(), contentResolver);
+    }
+
+    /**
+     * Default implementation of {@link NameLookupQuery} that looks up the name of a contact in the
+     * contacts database.
+     */
+    private static final class DefaultNameLookupQuery implements NameLookupQuery {
+        private static final String[] PROJECTION = { PhoneLookup.DISPLAY_NAME };
+        private static final int DISPLAY_NAME_COLUMN_INDEX = 0;
+
+        private final ContentResolver mContentResolver;
+        private final Context mContext;
+
+        private DefaultNameLookupQuery(Context context, ContentResolver contentResolver) {
+            mContext = context;
+            mContentResolver = contentResolver;
+        }
+
+        @Override
+        @Nullable
+        public String query(@Nullable String number) {
+            if (!PermissionsUtil.hasPermission(mContext, READ_CONTACTS)) {
+                Log.w(TAG, "No READ_CONTACTS permission, returning null for name lookup.");
+                return null;
+            }
+            try (Cursor cursor =  mContentResolver.query(
+                    Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
+                    PROJECTION, null, null, null)) {
+                if (cursor == null || !cursor.moveToFirst()) {
+                    return null;
+                }
+                return cursor.getString(DISPLAY_NAME_COLUMN_INDEX);
+            } catch (RuntimeException e) {
+                Log.w(TAG, "Exception when querying Contacts Provider for name lookup");
+                return null;
+            }
+        }
+    }
 }
diff --git a/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java b/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java
index 2b894a1..db82295 100644
--- a/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java
+++ b/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java
@@ -19,42 +19,41 @@
 import static android.Manifest.permission.READ_CALL_LOG;
 import static android.Manifest.permission.READ_CONTACTS;
 
+import com.android.contacts.common.ContactsUtils;
+import com.android.contacts.common.compat.TelephonyManagerCompat;
 import com.google.common.collect.Maps;
 
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.database.Cursor;
 import android.net.Uri;
-import android.provider.CallLog.Calls;
-import android.provider.ContactsContract.PhoneLookup;
-import android.telephony.PhoneNumberUtils;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.common.io.MoreCloseables;
 import com.android.contacts.common.util.ContactDisplayUtils;
-import com.android.contacts.common.util.PermissionsUtil;
 import com.android.dialer.DialtactsActivity;
 import com.android.dialer.R;
+import com.android.dialer.calllog.CallLogNotificationsHelper.NewCall;
 import com.android.dialer.filterednumber.FilteredNumbersUtil;
 import com.android.dialer.list.ListsFragment;
 
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
 /**
- * VoicemailNotifier that shows a notification in the status bar.
+ * Shows a voicemail notification in the status bar.
  */
 public class DefaultVoicemailNotifier {
-    public static final String TAG = "DefaultVoicemailNotifier";
+    public static final String TAG = "VoicemailNotifier";
 
     /** The tag used to identify notifications from this class. */
     private static final String NOTIFICATION_TAG = "DefaultVoicemailNotifier";
@@ -65,30 +64,18 @@
     private static DefaultVoicemailNotifier sInstance;
 
     private final Context mContext;
-    private final NotificationManager mNotificationManager;
-    private final NewCallsQuery mNewCallsQuery;
-    private final NameLookupQuery mNameLookupQuery;
 
     /** Returns the singleton instance of the {@link DefaultVoicemailNotifier}. */
-    public static synchronized DefaultVoicemailNotifier getInstance(Context context) {
+    public static DefaultVoicemailNotifier getInstance(Context context) {
         if (sInstance == null) {
-            NotificationManager notificationManager =
-                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
             ContentResolver contentResolver = context.getContentResolver();
-            sInstance = new DefaultVoicemailNotifier(context, notificationManager,
-                    createNewCallsQuery(context, contentResolver),
-                    createNameLookupQuery(context, contentResolver));
+            sInstance = new DefaultVoicemailNotifier(context);
         }
         return sInstance;
     }
 
-    private DefaultVoicemailNotifier(Context context,
-            NotificationManager notificationManager, NewCallsQuery newCallsQuery,
-            NameLookupQuery nameLookupQuery) {
+    private DefaultVoicemailNotifier(Context context) {
         mContext = context;
-        mNotificationManager = notificationManager;
-        mNewCallsQuery = newCallsQuery;
-        mNameLookupQuery = nameLookupQuery;
     }
 
     /**
@@ -102,16 +89,17 @@
     public void updateNotification(Uri newCallUri) {
         // Lookup the list of new voicemails to include in the notification.
         // TODO: Move this into a service, to avoid holding the receiver up.
-        final List<NewCall> newCalls = mNewCallsQuery.query();
+        final List<NewCall> newCalls =
+                CallLogNotificationsHelper.getInstance(mContext).getNewVoicemails();
 
         if (newCalls == null) {
             // Query failed, just return.
             return;
         }
 
-        if (newCalls.size() == 0) {
+        if (newCalls.isEmpty()) {
             // No voicemails to notify about: clear the notification.
-            clearNotification();
+            getNotificationManager().cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
             return;
         }
 
@@ -145,19 +133,8 @@
             // Check if we already know the name associated with this number.
             String name = names.get(newCall.number);
             if (name == null) {
-                name = PhoneNumberDisplayUtil.getDisplayName(
-                        mContext,
-                        newCall.number,
-                        newCall.numberPresentation,
-                        /* isVoicemail */ false).toString();
-                // If we cannot lookup the contact, use the number instead.
-                if (TextUtils.isEmpty(name)) {
-                    // Look it up in the database.
-                    name = mNameLookupQuery.query(newCall.number);
-                    if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(newCall.number)) {
-                        name = PhoneNumberUtils.formatNumber(newCall.number, newCall.countryIso);
-                    }
-                }
+                name = CallLogNotificationsHelper.getInstance(mContext).getName(newCall.number,
+                        newCall.numberPresentation, newCall.countryIso);
                 names.put(newCall.number, name);
                 // This is a new caller. Add it to the back of the list of callers.
                 if (TextUtils.isEmpty(callers)) {
@@ -174,6 +151,7 @@
             }
         }
 
+        // All the potential new voicemails have been removed, e.g. if they were spam.
         if (newCalls.isEmpty()) {
             return;
         }
@@ -194,13 +172,31 @@
         // TODO: Use the photo of contact if all calls are from the same person.
         final int icon = android.R.drawable.stat_notify_voicemail;
 
+        Uri ringtoneUri = null;
+        int notificationDefaults = 0;
+        if (callToNotify != null) {
+            PhoneAccountHandle accountHandle = new PhoneAccountHandle(
+                    ComponentName.unflattenFromString(callToNotify.accountComponentName),
+                    callToNotify.accountId);
+            ringtoneUri = TelephonyManagerCompat
+                    .getVoicemailRingtoneUri(getTelephonyManager(), accountHandle);
+            if (ContactsUtils.FLAG_N_FEATURE) {
+                notificationDefaults = TelephonyManagerCompat.isVoicemailVibrationEnabled(
+                        getTelephonyManager(), accountHandle)
+                        ? Notification.DEFAULT_VIBRATE : 0;
+            } else {
+                notificationDefaults = Notification.DEFAULT_ALL;
+            }
+        }
+
         Notification.Builder notificationBuilder = new Notification.Builder(mContext)
                 .setSmallIcon(icon)
                 .setContentTitle(title)
                 .setContentText(callers)
                 .setStyle(new Notification.BigTextStyle().bigText(transcription))
                 .setColor(resources.getColor(R.color.dialer_theme_color))
-                .setDefaults(callToNotify != null ? Notification.DEFAULT_ALL : 0)
+                .setSound(ringtoneUri)
+                .setDefaults(notificationDefaults)
                 .setDeleteIntent(createMarkNewVoicemailsAsOldIntent())
                 .setAutoCancel(true);
 
@@ -221,7 +217,8 @@
             notificationBuilder.setTicker(msg);
         }
 
-        mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notificationBuilder.build());
+        getNotificationManager().notify(NOTIFICATION_TAG, NOTIFICATION_ID,
+                notificationBuilder.build());
     }
 
     /** Creates a pending intent that marks all new voicemails as old. */
@@ -231,194 +228,11 @@
         return PendingIntent.getService(mContext, 0, intent, 0);
     }
 
-    public void clearNotification() {
-        mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
+    private NotificationManager getNotificationManager() {
+        return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
     }
 
-    /** Information about a new voicemail. */
-    private static final class NewCall {
-        public final Uri callsUri;
-        public final Uri voicemailUri;
-        public final String number;
-        public final int numberPresentation;
-        public final String accountComponentName;
-        public final String accountId;
-        public final String transcription;
-        public final String countryIso;
-        public final long dateMs;
-
-        public NewCall(
-                Uri callsUri,
-                Uri voicemailUri,
-                String number,
-                int numberPresentation,
-                String accountComponentName,
-                String accountId,
-                String transcription,
-                String countryIso,
-                long dateMs) {
-            this.callsUri = callsUri;
-            this.voicemailUri = voicemailUri;
-            this.number = number;
-            this.numberPresentation = numberPresentation;
-            this.accountComponentName = accountComponentName;
-            this.accountId = accountId;
-            this.transcription = transcription;
-            this.countryIso = countryIso;
-            this.dateMs = dateMs;
-        }
-    }
-
-    /** Allows determining the new calls for which a notification should be generated. */
-    public interface NewCallsQuery {
-        /**
-         * Returns the new calls for which a notification should be generated.
-         */
-        public List<NewCall> query();
-    }
-
-    /** Create a new instance of {@link NewCallsQuery}. */
-    public static NewCallsQuery createNewCallsQuery(Context context,
-            ContentResolver contentResolver) {
-        return new DefaultNewCallsQuery(context.getApplicationContext(), contentResolver);
-    }
-
-    /**
-     * Default implementation of {@link NewCallsQuery} that looks up the list of new calls to
-     * notify about in the call log.
-     */
-    private static final class DefaultNewCallsQuery implements NewCallsQuery {
-        private static final String[] PROJECTION = {
-            Calls._ID,
-            Calls.NUMBER,
-            Calls.VOICEMAIL_URI,
-            Calls.NUMBER_PRESENTATION,
-            Calls.PHONE_ACCOUNT_COMPONENT_NAME,
-            Calls.PHONE_ACCOUNT_ID,
-            Calls.TRANSCRIPTION,
-            Calls.COUNTRY_ISO,
-            Calls.DATE
-        };
-        private static final int ID_COLUMN_INDEX = 0;
-        private static final int NUMBER_COLUMN_INDEX = 1;
-        private static final int VOICEMAIL_URI_COLUMN_INDEX = 2;
-        private static final int NUMBER_PRESENTATION_COLUMN_INDEX = 3;
-        private static final int PHONE_ACCOUNT_COMPONENT_NAME_COLUMN_INDEX = 4;
-        private static final int PHONE_ACCOUNT_ID_COLUMN_INDEX = 5;
-        private static final int TRANSCRIPTION_COLUMN_INDEX = 6;
-        private static final int COUNTRY_ISO_COLUMN_INDEX = 7;
-        private static final int DATE_COLUMN_INDEX = 8;
-
-        private final ContentResolver mContentResolver;
-        private final Context mContext;
-
-        private DefaultNewCallsQuery(Context context, ContentResolver contentResolver) {
-            mContext = context;
-            mContentResolver = contentResolver;
-        }
-
-        @Override
-        public List<NewCall> query() {
-            if (!PermissionsUtil.hasPermission(mContext, READ_CALL_LOG)) {
-                Log.w(TAG, "No READ_CALL_LOG permission, returning null for calls lookup.");
-                return null;
-            }
-            final String selection = String.format("%s = 1 AND %s = ?", Calls.NEW, Calls.TYPE);
-            final String[] selectionArgs = new String[]{ Integer.toString(Calls.VOICEMAIL_TYPE) };
-            Cursor cursor = null;
-            try {
-                cursor = mContentResolver.query(Calls.CONTENT_URI_WITH_VOICEMAIL, PROJECTION,
-                        selection, selectionArgs, Calls.DEFAULT_SORT_ORDER);
-                if (cursor == null) {
-                    return null;
-                }
-                List<NewCall> newCalls = new LinkedList<NewCall>();
-                while (cursor.moveToNext()) {
-                    newCalls.add(createNewCallsFromCursor(cursor));
-                }
-                return newCalls;
-            } catch (RuntimeException e) {
-                Log.w(TAG, "Exception when querying Contacts Provider for calls lookup");
-                return null;
-            } finally {
-                MoreCloseables.closeQuietly(cursor);
-            }
-        }
-
-        /** Returns an instance of {@link NewCall} created by using the values of the cursor. */
-        private NewCall createNewCallsFromCursor(Cursor cursor) {
-            String voicemailUriString = cursor.getString(VOICEMAIL_URI_COLUMN_INDEX);
-            Uri callsUri = ContentUris.withAppendedId(
-                    Calls.CONTENT_URI_WITH_VOICEMAIL, cursor.getLong(ID_COLUMN_INDEX));
-            Uri voicemailUri = voicemailUriString == null ? null : Uri.parse(voicemailUriString);
-            return new NewCall(
-                    callsUri,
-                    voicemailUri,
-                    cursor.getString(NUMBER_COLUMN_INDEX),
-                    cursor.getInt(NUMBER_PRESENTATION_COLUMN_INDEX),
-                    cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME_COLUMN_INDEX),
-                    cursor.getString(PHONE_ACCOUNT_ID_COLUMN_INDEX),
-                    cursor.getString(TRANSCRIPTION_COLUMN_INDEX),
-                    cursor.getString(COUNTRY_ISO_COLUMN_INDEX),
-                    cursor.getLong(DATE_COLUMN_INDEX));
-        }
-    }
-
-    /** Allows determining the name associated with a given phone number. */
-    public interface NameLookupQuery {
-        /**
-         * Returns the name associated with the given number in the contacts database, or null if
-         * the number does not correspond to any of the contacts.
-         * <p>
-         * If there are multiple contacts with the same phone number, it will return the name of one
-         * of the matching contacts.
-         */
-        public String query(String number);
-    }
-
-    /** Create a new instance of {@link NameLookupQuery}. */
-    public static NameLookupQuery createNameLookupQuery(Context context,
-            ContentResolver contentResolver) {
-        return new DefaultNameLookupQuery(context.getApplicationContext(), contentResolver);
-    }
-
-    /**
-     * Default implementation of {@link NameLookupQuery} that looks up the name of a contact in the
-     * contacts database.
-     */
-    private static final class DefaultNameLookupQuery implements NameLookupQuery {
-        private static final String[] PROJECTION = { PhoneLookup.DISPLAY_NAME };
-        private static final int DISPLAY_NAME_COLUMN_INDEX = 0;
-
-        private final ContentResolver mContentResolver;
-        private final Context mContext;
-
-        private DefaultNameLookupQuery(Context context, ContentResolver contentResolver) {
-            mContext = context;
-            mContentResolver = contentResolver;
-        }
-
-        @Override
-        public String query(String number) {
-            if (!PermissionsUtil.hasPermission(mContext, READ_CONTACTS)) {
-                Log.w(TAG, "No READ_CONTACTS permission, returning null for name lookup.");
-                return null;
-            }
-            Cursor cursor = null;
-            try {
-                cursor = mContentResolver.query(
-                        Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
-                        PROJECTION, null, null, null);
-                if (cursor == null || !cursor.moveToFirst()) return null;
-                return cursor.getString(DISPLAY_NAME_COLUMN_INDEX);
-            } catch (RuntimeException e) {
-                Log.w(TAG, "Exception when querying Contacts Provider for name lookup");
-                return null;
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-        }
+    private TelephonyManager getTelephonyManager() {
+        return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
     }
 }
diff --git a/src/com/android/dialer/calllog/PhoneNumberDisplayUtil.java b/src/com/android/dialer/calllog/PhoneNumberDisplayUtil.java
index 91cd3e1..09b42e9 100644
--- a/src/com/android/dialer/calllog/PhoneNumberDisplayUtil.java
+++ b/src/com/android/dialer/calllog/PhoneNumberDisplayUtil.java
@@ -78,7 +78,7 @@
         } else if (!TextUtils.isEmpty(number)) {
             return number.toString() + postDialDigits;
         } else {
-            return "";
+            return context.getResources().getString(R.string.unknown);
         }
     }
 
diff --git a/src/com/android/dialer/compat/UserManagerCompat.java b/src/com/android/dialer/compat/UserManagerCompat.java
index a1fdcc6..5767033 100644
--- a/src/com/android/dialer/compat/UserManagerCompat.java
+++ b/src/com/android/dialer/compat/UserManagerCompat.java
@@ -1,3 +1,18 @@
+/*
+ * 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;
@@ -34,4 +49,23 @@
         // Adapted from {@link UserManager} and {@link UserHandle}.
         return (Process.myUid() / PER_USER_RANGE) == USER_SYSTEM;
     }
+
+    /**
+     * Return whether the calling user is running in an "unlocked" state. A user
+     * is unlocked only after they've entered their credentials (such as a lock
+     * pattern or PIN), and credential-encrypted private app data storage is
+     * available.
+     *
+     * TODO b/26688153
+     *
+     * @param context the current context
+     * @return {@code true} if the user is unlocked, {@code false} otherwise
+     * @throws NullPointerException if context is null
+     */
+    public static boolean isUserUnlocked(Context context) {
+        if (CompatUtils.isNCompatible()) {
+            return UserManagerSdkCompat.isUserUnlocked(context);
+        }
+        return true;
+    }
 }
diff --git a/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java b/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java
index 06aca78..ff9b1be 100644
--- a/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java
+++ b/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java
@@ -134,7 +134,7 @@
                 new Listener() {
                     @Override
                     protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-                        listener.onHasBlockedNumbers(cursor.getCount() > 0);
+                        listener.onHasBlockedNumbers(cursor != null && cursor.getCount() > 0);
                     }
                 },
                 getContentUri(null),
@@ -161,7 +161,7 @@
                 new Listener() {
                     @Override
                     protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-                        if (cursor.getCount() != 1) {
+                        if (cursor == null || cursor.getCount() != 1) {
                             listener.onCheckComplete(null);
                             return;
                         }
@@ -246,9 +246,10 @@
         startQuery(NO_TOKEN, new Listener() {
             @Override
             public void onQueryComplete(int token, Object cookie, Cursor cursor) {
-                if (cursor.getCount() != 1) {
+                int rowsReturned = cursor == null ? 0 : cursor.getCount();
+                if (rowsReturned != 1) {
                     throw new SQLiteDatabaseCorruptException
-                            ("Returned " + cursor.getCount() + " rows for uri "
+                            ("Returned " + rowsReturned + " rows for uri "
                                     + uri + "where 1 expected.");
                 }
                 cursor.moveToFirst();
diff --git a/src/com/android/dialer/service/SpamButtonRenderer.java b/src/com/android/dialer/service/SpamButtonRenderer.java
new file mode 100644
index 0000000..843bc55
--- /dev/null
+++ b/src/com/android/dialer/service/SpamButtonRenderer.java
@@ -0,0 +1,23 @@
+package com.android.dialer.service;
+
+import android.view.View;
+
+import java.util.List;
+
+/**
+ * Interface responsible for rendering spam buttons.
+ */
+public interface SpamButtonRenderer {
+
+    /**
+     * Renders buttons for a phone number.
+     */
+    void render(String number, String countryIso);
+
+    void setCompleteListItemViews(List<View> views);
+
+    void setSpamFilteredViews(List<View> views);
+
+    void setFilteredNumberViews(List<View> views);
+
+}
diff --git a/src/com/android/dialer/settings/AppCompatPreferenceActivity.java b/src/com/android/dialer/settings/AppCompatPreferenceActivity.java
deleted file mode 100644
index 4e5d9c9..0000000
--- a/src/com/android/dialer/settings/AppCompatPreferenceActivity.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * 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.settings;
-
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.preference.PreferenceActivity;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.AppCompatDelegate;
-import android.support.v7.widget.Toolbar;
-import android.view.MenuInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
- * to be used with AppCompat.
- */
-public class AppCompatPreferenceActivity extends PreferenceActivity {
-    private AppCompatDelegate mDelegate;
-
-    private boolean mIsSafeToCommitTransactions;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        getDelegate().installViewFactory();
-        getDelegate().onCreate(savedInstanceState);
-        super.onCreate(savedInstanceState);
-        mIsSafeToCommitTransactions = true;
-    }
-
-    @Override
-    protected void onPostCreate(Bundle savedInstanceState) {
-        super.onPostCreate(savedInstanceState);
-        getDelegate().onPostCreate(savedInstanceState);
-    }
-
-    public ActionBar getSupportActionBar() {
-        return getDelegate().getSupportActionBar();
-    }
-
-    public void setSupportActionBar(Toolbar toolbar) {
-        getDelegate().setSupportActionBar(toolbar);
-    }
-
-    @Override
-    public MenuInflater getMenuInflater() {
-        return getDelegate().getMenuInflater();
-    }
-
-    @Override
-    public void setContentView(int layoutResID) {
-        getDelegate().setContentView(layoutResID);
-    }
-
-    @Override
-    public void setContentView(View view) {
-        getDelegate().setContentView(view);
-    }
-
-    @Override
-    public void setContentView(View view, ViewGroup.LayoutParams params) {
-        getDelegate().setContentView(view, params);
-    }
-
-    @Override
-    public void addContentView(View view, ViewGroup.LayoutParams params) {
-        getDelegate().addContentView(view, params);
-    }
-
-    @Override
-    protected void onPostResume() {
-        super.onPostResume();
-        getDelegate().onPostResume();
-    }
-
-    @Override
-    protected void onTitleChanged(CharSequence title, int color) {
-        super.onTitleChanged(title, color);
-        getDelegate().setTitle(title);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        getDelegate().onConfigurationChanged(newConfig);
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        getDelegate().onStop();
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        getDelegate().onDestroy();
-    }
-
-    @Override
-    public void invalidateOptionsMenu() {
-        getDelegate().invalidateOptionsMenu();
-    }
-
-    private AppCompatDelegate getDelegate() {
-        if (mDelegate == null) {
-            mDelegate = AppCompatDelegate.create(this, null);
-        }
-        return mDelegate;
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        mIsSafeToCommitTransactions = true;
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        mIsSafeToCommitTransactions = true;
-    }
-
-    @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        mIsSafeToCommitTransactions = false;
-    }
-
-    /**
-     * Returns true if it is safe to commit {@link FragmentTransaction}s at this time, based on
-     * whether {@link Activity#onSaveInstanceState} has been called or not.
-     *
-     * Make sure that the current activity calls into
-     * {@link super.onSaveInstanceState(Bundle outState)} (if that method is overridden),
-     * so the flag is properly set.
-     */
-    public boolean isSafeToCommitTransactions() {
-        return mIsSafeToCommitTransactions;
-    }
-}
diff --git a/src/com/android/dialer/settings/DialerSettingsActivity.java b/src/com/android/dialer/settings/DialerSettingsActivity.java
index a187157..b25c5a6 100644
--- a/src/com/android/dialer/settings/DialerSettingsActivity.java
+++ b/src/com/android/dialer/settings/DialerSettingsActivity.java
@@ -28,6 +28,7 @@
 import android.view.MenuItem;
 import android.widget.Toast;
 
+import com.android.contacts.common.activity.AppCompatPreferenceActivity;
 import com.android.contacts.common.compat.CompatUtils;
 import com.android.contacts.common.compat.SdkVersionOverride;
 import com.android.contacts.common.compat.TelephonyManagerCompat;
diff --git a/src/com/android/dialerbind/ObjectFactory.java b/src/com/android/dialerbind/ObjectFactory.java
index f346a7e..d06d55e 100644
--- a/src/com/android/dialerbind/ObjectFactory.java
+++ b/src/com/android/dialerbind/ObjectFactory.java
@@ -19,12 +19,16 @@
 import static com.android.dialer.calllog.CallLogAdapter.CallFetcher;
 
 import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
 
 import com.android.dialer.calllog.CallLogAdapter;
 import com.android.dialer.calllog.ContactInfoHelper;
 import com.android.dialer.list.RegularSearchFragment;
 import com.android.dialer.logging.Logger;
 import com.android.dialer.service.CachedNumberLookupService;
+import com.android.dialer.service.SpamButtonRenderer;
 import com.android.dialer.voicemail.VoicemailPlaybackPresenter;
 
 /**
@@ -41,6 +45,12 @@
         return "com.android.dialer.database.filterednumberprovider";
     }
 
+    public static SpamButtonRenderer newSpamButtonRenderer(
+            Context context,
+            ViewStub stub) {
+        return null;
+    }
+
     /**
      * Create a new instance of the call log adapter.
      * @param context The context to use.
diff --git a/tests/src/com/android/dialer/compat/UserManagerCompatTest.java b/tests/src/com/android/dialer/compat/UserManagerCompatTest.java
new file mode 100644
index 0000000..ff734a1
--- /dev/null
+++ b/tests/src/com/android/dialer/compat/UserManagerCompatTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.test.AndroidTestCase;
+
+import com.android.contacts.common.compat.CompatUtils;
+
+public class UserManagerCompatTest extends AndroidTestCase {
+
+    public void testIsUserUnlocked_N_NullContext() {
+        if (!CompatUtils.isNCompatible()) {
+            return;
+        }
+        try {
+            UserManagerCompat.isUserUnlocked(null);
+            fail("Expected NullPointerException but none was thrown");
+        } catch (NullPointerException e) {}
+    }
+
+    public void testIsUserUnlocked_M_NullContext() {
+        if (CompatUtils.isNCompatible()) {
+            return;
+        }
+        assertTrue(UserManagerCompat.isUserUnlocked(null));
+    }
+
+    public void testIsUserUnlocked() {
+        assertTrue(UserManagerCompat.isUserUnlocked(getContext()));
+    }
+}