Merge "Remove FeatureHighlight lib from AOSP contacts." into ub-contactsdialer-g-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index bd36560..7d7579d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,8 +16,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.contacts"
-    android:versionCode="10512"
-    android:versionName="1.5.12">
+    android:versionCode="20000"
+    android:versionName="2.0.0">
 
     <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="25" />
 
diff --git a/proguard.flags b/proguard.flags
index f1e609b..d9dad57 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -19,6 +19,7 @@
 -keep class com.android.contacts.common.database.NoNullCursorAsyncQueryHandler { *; }
 -keep class com.android.contacts.common.format.FormatUtils { *; }
 -keep class com.android.contacts.common.format.TextHighlighter { *; }
+-keep class com.android.contacts.common.list.ContactListFilter { *; }
 -keep class com.android.contacts.common.list.ContactListItemView { *; }
 -keep class com.android.contacts.common.list.ContactsSectionIndexer { *; }
 -keep class com.android.contacts.common.location.CountryDetector { *; }
@@ -66,6 +67,11 @@
 -keep class com.android.contacts.common.util.NameConverter { *; }
 -keep class com.android.contacts.common.util.SearchUtil { *; }
 -keep class com.android.contacts.common.util.SearchUtil$* { *; }
+-keep class com.android.contacts.common.util.DeviceAccountFilter { *; }
+-keep class com.android.contacts.common.util.DeviceAccountFilter$* { *; }
+-keep class com.android.contacts.common.util.DeviceAccountPresentationValues { *; }
+-keep class com.android.contacts.common.util.DeviceAccountPresentationValues$* { *; }
+-keep class com.android.contacts.common.util.DeviceLocalContactsFilterProvider { *; }
 -keep class com.android.contacts.ContactsApplication { *; }
 -keep class com.android.contacts.ContactSaveService { *; }
 -keep class com.android.contacts.ContactSaveService$* { *; }
@@ -86,11 +92,9 @@
 -keep class com.google.common.collect.Multimap { *; }
 -keep class com.google.common.collect.Sets { *; }
 
-# Any class or method annotated with NeededForTesting or NeededForReflection.
--keep @com.android.contacts.common.testing.NeededForTesting class *
+# Any class or method annotated with NeededForReflection.
 -keep @com.android.contacts.test.NeededForReflection class *
 -keepclassmembers class * {
-@com.android.contacts.common.testing.NeededForTesting *;
 @com.android.contacts.test.NeededForReflection *;
 }
 
diff --git a/res/drawable/ic_sim_card_black_24dp.xml b/res/drawable/ic_sim_card_black_24dp.xml
new file mode 100644
index 0000000..40ee84f
--- /dev/null
+++ b/res/drawable/ic_sim_card_black_24dp.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<!-- Material design SIM card icon https://design.google.com/icons/#ic_sim_card -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M19.99,4c0,-1.1 -0.89,-2 -1.99,-2h-8L4,8v12c0,1.1 0.9,2 2,2h12.01c1.1,0 1.99,-0.9 1.99,-2l-0.01,-16zM9,19L7,19v-2h2v2zM17,19h-2v-2h2v2zM9,15L7,15v-4h2v4zM13,19h-2v-4h2v4zM13,13h-2v-2h2v2zM17,15h-2v-4h2v4z"/>
+</vector>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 37ea0ba..355a4e8 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -187,13 +187,8 @@
 
     <color name="letter_tile_font_color">#ffffff</color>
 
-    <!-- Background color of action bars. Ensure this stays in sync with packages/Telephony
-         actionbar_background_color. -->
-    <color name="actionbar_background_color">#0fc6dc</color>
     <!-- Color for icons in the actionbar -->
     <color name="actionbar_icon_color">#ffffff</color>
-    <!-- Darker version of the actionbar color. Used for the status bar and navigation bar colors. -->
-    <color name="actionbar_background_color_dark">#008aa1</color>
 
     <color name="tab_ripple_color">@color/tab_accent_color</color>
     <color name="tab_accent_color">#ffffff</color>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c4971f1..14207d2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -943,11 +943,6 @@
          The user can click on the action to rollback the modification-->
     <string name="undo">Undo</string>
 
-    <!-- Toast shown when text is copied to the clipboard [CHAR LIMIT=64] -->
-    <string name="toast_text_copied">Text copied</string>
-    <!-- Option displayed in context menu to copy long pressed item to clipboard [CHAR LIMIT=64] -->
-    <string name="copy_text">Copy to clipboard</string>
-
     <!-- Action string for calling a custom phone number -->
     <string name="call_custom">Call
         <xliff:g id="custom_label" example="business">%s</xliff:g>
@@ -1070,9 +1065,6 @@
     <!--  Used to display as default status when the contact is busy or Do not disturb for chat [CHAR LIMIT=19] -->
     <string name="status_busy">Busy</string>
 
-    <!-- Directory partition name (also exists in contacts) -->
-    <string name="contactsList">Contacts</string>
-
     <!-- The name of the invisible local contact directory -->
     <string name="local_invisible_directory">Other</string>
 
@@ -1085,23 +1077,9 @@
     <!-- The label in section header in the contact list for a local contacts [CHAR LIMIT=128] -->
     <string name="local_search_label">All contacts</string>
 
-    <!-- Title shown in the search result activity of contacts app while searching.  [CHAR LIMIT=20]
-         (also in contacts) -->
-    <string name="search_results_searching">Searching\u2026</string>
-
     <!-- Displayed at the top of search results indicating that more contacts were found than shown [CHAR LIMIT=64] -->
     <string name="foundTooManyContacts">More than <xliff:g id="count">%d</xliff:g> found.</string>
 
-    <!-- Displayed at the top of the contacts showing the zero total number of contacts found when "Only contacts with phones" not selected. [CHAR LIMIT=30]
-         (also in contacts) -->
-    <string name="listFoundAllContactsZero">No contacts</string>
-
-    <!-- Displayed at the top of the contacts showing the total number of contacts found when typing search query -->
-    <plurals name="searchFoundContacts">
-        <item quantity="one">1 found</item>
-        <item quantity="other"><xliff:g id="count">%d</xliff:g> found</item>
-    </plurals>
-
     <!-- String describing the text for photo of a contact in a contacts list.
 
         Note: AccessibilityServices use this attribute to announce what the view represents.
@@ -1137,6 +1115,11 @@
          and will not be synced. [CHAR LIMIT=20]  -->
     <string name="account_phone">Device</string>
 
+    <!-- Title for data source when creating or editing a contact that is stored on the
+         devices SIM card. This contact will only exist on the phone and will not be synced.
+         [CHAR LIMIT=20]  -->
+    <string name="account_sim">SIM</string>
+
     <!-- Header that expands to list all name types when editing a structured name of a contact
          [CHAR LIMIT=20] -->
     <string name="nameLabelsGroup">Name</string>
@@ -1589,9 +1572,6 @@
     <!-- Label of the "About" setting -->
     <string name="setting_about">About Contacts</string>
 
-    <!-- Title of the settings activity [CHAR LIMIT=64] -->
-    <string name="activity_title_settings">Settings</string>
-
     <!-- Action that shares visible contacts -->
     <string name="share_visible_contacts">Share visible contacts</string>
 
@@ -1707,7 +1687,7 @@
     <string name="call_with_a_note">Call with a note</string>
 
     <!-- Hint text shown in the call subject dialog. [CHAR LIMIT=255] -->
-    <string name="call_subject_hint">Type a note to send with call ...</string>
+    <string name="call_subject_hint">Type a note to send with call&#8230;</string>
 
     <!-- Button used to start a new call with the user entered subject. [CHAR LIMIT=32] -->
     <string name="send_and_call_button">SEND &amp; CALL</string>
diff --git a/src-bind/com/android/contactsbind/ObjectFactory.java b/src-bind/com/android/contactsbind/ObjectFactory.java
index 1a5b346..f6dc0c7 100644
--- a/src-bind/com/android/contactsbind/ObjectFactory.java
+++ b/src-bind/com/android/contactsbind/ObjectFactory.java
@@ -15,6 +15,8 @@
 
 import com.android.contacts.common.logging.Logger;
 import com.android.contacts.common.preference.PreferenceManager;
+import com.android.contacts.common.util.DeviceAccountFilter;
+import com.android.contacts.common.util.DeviceAccountPresentationValues;
 
 import android.content.Context;
 
@@ -28,4 +30,12 @@
     }
 
     public static PreferenceManager getPreferenceManager(Context context) { return null; }
+
+    public static DeviceAccountPresentationValues createDeviceAccountPresentationValues(Context context) {
+        return new DeviceAccountPresentationValues.Default(context);
+    }
+
+    public static DeviceAccountFilter getDeviceAccountFilter(Context context) {
+        return DeviceAccountFilter.ONLY_NULL;
+    }
 }
diff --git a/src/com/android/contacts/ContactsDrawerActivity.java b/src/com/android/contacts/ContactsDrawerActivity.java
index bea1411..f976807 100644
--- a/src/com/android/contacts/ContactsDrawerActivity.java
+++ b/src/com/android/contacts/ContactsDrawerActivity.java
@@ -59,6 +59,7 @@
 import com.android.contacts.common.preference.ContactsPreferenceActivity;
 import com.android.contacts.common.util.AccountFilterUtil;
 import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter;
+import com.android.contacts.common.util.DeviceAccountPresentationValues;
 import com.android.contacts.common.util.ImplicitIntentsUtil;
 import com.android.contacts.common.util.ViewUtil;
 import com.android.contacts.editor.ContactEditorFragment;
@@ -74,6 +75,7 @@
 import com.android.contacts.util.SharedPreferenceUtil;
 import com.android.contactsbind.Assistants;
 import com.android.contactsbind.HelpUtils;
+import com.android.contactsbind.ObjectFactory;
 
 import java.util.HashMap;
 import java.util.Iterator;
@@ -171,6 +173,7 @@
 
     // The account the new group will be created under.
     private AccountWithDataSet mNewGroupAccount;
+    private DeviceAccountPresentationValues mDeviceAccountPresentationValues;
 
     private int mPositionOfLastGroup;
 
@@ -183,6 +186,8 @@
 
         super.setContentView(R.layout.contacts_drawer_activity);
 
+        mDeviceAccountPresentationValues = ObjectFactory.createDeviceAccountPresentationValues(this);
+
         // Set up the action bar.
         mToolbar = getView(R.id.toolbar);
         setSupportActionBar(mToolbar);
@@ -468,12 +473,12 @@
 
         int positionOfLastFilter = mPositionOfLastGroup + GAP_BETWEEN_TWO_MENU_GROUPS;
 
+        mDeviceAccountPresentationValues.setFilters(accountFilterItems);
+
         for (int i = 0; i < accountFilterItems.size(); i++) {
             positionOfLastFilter++;
             final ContactListFilter filter = accountFilterItems.get(i);
-            final String menuName =
-                    filter.filterType == ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS
-                            ? getString(R.string.account_phone) : filter.accountName;
+            final CharSequence menuName = mDeviceAccountPresentationValues.getLabel(i);
             final MenuItem menuItem = subMenu.add(R.id.nav_filters_items, Menu.NONE,
                     positionOfLastFilter, menuName);
             mFilterMenuMap.put(filter, menuItem);
@@ -497,7 +502,7 @@
                     return true;
                 }
             });
-            menuItem.setIcon(filter.icon);
+            menuItem.setIcon(mDeviceAccountPresentationValues.getIcon(i));
             // Get rid of the default menu item overlay and show original account icons.
             menuItem.getIcon().setColorFilter(Color.TRANSPARENT, PorterDuff.Mode.SRC_ATOP);
             // Create a dummy action view to attach extra hidden content description to the menuItem
diff --git a/src/com/android/contacts/common/list/ContactListFilter.java b/src/com/android/contacts/common/list/ContactListFilter.java
index 6d60a82..e99c374 100644
--- a/src/com/android/contacts/common/list/ContactListFilter.java
+++ b/src/com/android/contacts/common/list/ContactListFilter.java
@@ -181,6 +181,8 @@
         int code = filterType;
         if (accountType != null) {
             code = code * 31 + accountType.hashCode();
+        }
+        if (accountName != null) {
             code = code * 31 + accountName.hashCode();
         }
         if (dataSet != null) {
diff --git a/src/com/android/contacts/common/list/ContactListFilterController.java b/src/com/android/contacts/common/list/ContactListFilterController.java
index 48d36ed..4d3d6ad 100644
--- a/src/com/android/contacts/common/list/ContactListFilterController.java
+++ b/src/com/android/contacts/common/list/ContactListFilterController.java
@@ -184,12 +184,6 @@
                             ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS), true, notifyListeners);
                 }
                 break;
-            case ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS:
-                if (!localAccountExists()) {
-                    setContactListFilter(ContactListFilter.createFilterWithType(
-                            ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS), true, notifyListeners);
-                }
-                break;
         }
     }
 
@@ -202,13 +196,4 @@
                 mFilter.accountName, mFilter.accountType, mFilter.dataSet);
         return accountTypeManager.contains(filterAccount, /* contactWritableOnly */ false);
     }
-
-    /**
-     * @return true if the local account still exists.
-     */
-    private boolean localAccountExists() {
-        final AccountTypeManager accountTypeManager = AccountTypeManager.getInstance(mContext);
-        final AccountWithDataSet localAccount = AccountWithDataSet.getLocalAccount();
-        return accountTypeManager.contains(localAccount, /* contactWritableOnly */ false);
-    }
 }
diff --git a/src/com/android/contacts/common/list/DefaultContactListAdapter.java b/src/com/android/contacts/common/list/DefaultContactListAdapter.java
index 666de8c..43cca1a 100644
--- a/src/com/android/contacts/common/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/common/list/DefaultContactListAdapter.java
@@ -245,7 +245,18 @@
                 break;
             }
             case ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS: {
-                selection.append(AccountWithDataSet.LOCAL_ACCOUNT_SELECTION);
+                if (filter.accountType != null) {
+                    selection.append(ContactsContract.RawContacts.ACCOUNT_TYPE)
+                            .append("=?");
+                    selectionArgs.add(filter.accountType);
+                    if (filter.accountName != null) {
+                        selection.append(" AND ").append(ContactsContract.RawContacts.ACCOUNT_NAME)
+                                .append(("=?"));
+                        selectionArgs.add(filter.accountName);
+                    }
+                } else {
+                    selection.append(AccountWithDataSet.LOCAL_ACCOUNT_SELECTION);
+                }
                 break;
             }
         }
diff --git a/src/com/android/contacts/common/model/AccountTypeManager.java b/src/com/android/contacts/common/model/AccountTypeManager.java
index aaf1476..35a7a3a 100644
--- a/src/com/android/contacts/common/model/AccountTypeManager.java
+++ b/src/com/android/contacts/common/model/AccountTypeManager.java
@@ -53,6 +53,8 @@
 import com.android.contacts.common.model.account.SamsungAccountType;
 import com.android.contacts.common.model.dataitem.DataKind;
 import com.android.contacts.common.util.Constants;
+import com.android.contacts.common.util.DeviceAccountFilter;
+import com.android.contactsbind.ObjectFactory;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Objects;
 import com.google.common.collect.Lists;
@@ -63,6 +65,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -87,7 +90,8 @@
         synchronized (mInitializationLock) {
             if (mAccountTypeManager == null) {
                 context = context.getApplicationContext();
-                mAccountTypeManager = new AccountTypeManagerImpl(context);
+                mAccountTypeManager = new AccountTypeManagerImpl(context,
+                        ObjectFactory.getDeviceAccountFilter(context));
             }
         }
         return mAccountTypeManager;
@@ -247,6 +251,7 @@
 
     private Context mContext;
     private AccountManager mAccountManager;
+    private DeviceAccountFilter mDeviceAccountFilter;
 
     private AccountType mFallbackAccountType;
 
@@ -301,9 +306,10 @@
     /**
      * Internal constructor that only performs initial parsing.
      */
-    public AccountTypeManagerImpl(Context context) {
+    public AccountTypeManagerImpl(Context context, DeviceAccountFilter deviceAccountFilter) {
         mContext = context;
         mFallbackAccountType = new FallbackAccountType(context);
+        mDeviceAccountFilter = deviceAccountFilter;
 
         mAccountManager = AccountManager.get(mContext);
 
@@ -437,6 +443,8 @@
             } else if (SamsungAccountType.isSamsungAccountType(mContext, type,
                     auth.packageName)) {
                 accountType = new SamsungAccountType(mContext, auth.packageName, type);
+            } else if (mDeviceAccountFilter.isDeviceAccountType(type)) {
+                accountType = new FallbackAccountType(mContext);
             } else {
                 Log.d(TAG, "Registering external account type=" + type
                         + ", packageName=" + auth.packageName);
@@ -452,9 +460,13 @@
                 }
             }
 
-            accountType.accountType = auth.type;
-            accountType.titleRes = auth.labelId;
-            accountType.iconRes = auth.iconId;
+            // TODO: this is a hack. For FallbackAccountType we want to use a default icon and
+            // label instead of what is pulled out of the authenticator
+            if (!(accountType instanceof FallbackAccountType)) {
+                accountType.accountType = auth.type;
+                accountType.titleRes = auth.labelId;
+                accountType.iconRes = auth.iconId;
+            }
 
             addAccountType(accountType, accountTypesByTypeAndDataSet, accountTypesByType);
 
diff --git a/src/com/android/contacts/common/util/AccountFilterUtil.java b/src/com/android/contacts/common/util/AccountFilterUtil.java
index 76975a6..2d59981 100644
--- a/src/com/android/contacts/common/util/AccountFilterUtil.java
+++ b/src/com/android/contacts/common/util/AccountFilterUtil.java
@@ -33,7 +33,6 @@
 import com.android.contacts.common.model.AccountTypeManager;
 import com.android.contacts.common.model.account.AccountType;
 import com.android.contacts.common.model.account.AccountWithDataSet;
-
 import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
diff --git a/src/com/android/contacts/common/util/DeviceAccountFilter.java b/src/com/android/contacts/common/util/DeviceAccountFilter.java
new file mode 100644
index 0000000..9dc98a5
--- /dev/null
+++ b/src/com/android/contacts/common/util/DeviceAccountFilter.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 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.contacts.common.util;
+
+/**
+ * Reports whether a value from RawContacts.ACCOUNT_TYPE should be considered a "Device"
+ * account
+ */
+public interface DeviceAccountFilter {
+    boolean isDeviceAccountType(String accountType);
+
+    public static DeviceAccountFilter ONLY_NULL = new DeviceAccountFilter() {
+        @Override
+        public boolean isDeviceAccountType(String accountType) {
+            return accountType == null;
+        }
+    };
+}
diff --git a/src/com/android/contacts/common/util/DeviceAccountPresentationValues.java b/src/com/android/contacts/common/util/DeviceAccountPresentationValues.java
new file mode 100644
index 0000000..dab81ed
--- /dev/null
+++ b/src/com/android/contacts/common/util/DeviceAccountPresentationValues.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2016 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.contacts.common.util;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import com.android.contacts.common.list.ContactListFilter;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Supplies the label and icon that should be used for device accounts in the Nav Drawer.
+ *
+ * This operates on the list of filters to allow the implementation to choose better resources
+ * in the case that there are multiple device accounts in the filter list.
+ */
+public interface DeviceAccountPresentationValues {
+    void setFilters(List<ContactListFilter> filters);
+
+    CharSequence getLabel(int index);
+
+    Drawable getIcon(int index);
+
+    /**
+     * The default implementation only returns a label and icon for a device filter that as null
+     * values for the accountType and accountName
+     */
+    class Default implements DeviceAccountPresentationValues {
+        private final Context mContext;
+
+        private List<ContactListFilter> mFilters = null;
+
+        public Default(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public CharSequence getLabel(int index) {
+            assertFiltersInitialized();
+
+            final ContactListFilter filter = mFilters.get(index);
+            if (filter.filterType != ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
+                return filter.accountName;
+            }
+            return filter.accountName != null ? filter.accountName :
+                    mContext.getString(com.android.contacts.common.R.string.account_phone);
+        }
+
+        @Override
+        public Drawable getIcon(int index) {
+            assertFiltersInitialized();
+
+            final ContactListFilter filter = mFilters.get(index);
+            if (filter.filterType != ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
+                return filter.icon;
+            }
+            return mContext.getDrawable(com.android.contacts.common.R.drawable.ic_device);
+        }
+
+        @Override
+        public void setFilters(List<ContactListFilter> filters) {
+            if (filters == null) {
+                mFilters = Collections.emptyList();
+            } else {
+                mFilters = filters;
+            }
+        }
+
+        private void assertFiltersInitialized() {
+            if (mFilters == null) {
+                throw new IllegalStateException("setFilters must be called first.");
+            }
+        }
+    }
+
+}
diff --git a/src/com/android/contacts/common/util/DeviceLocalContactsFilterProvider.java b/src/com/android/contacts/common/util/DeviceLocalContactsFilterProvider.java
new file mode 100644
index 0000000..1d06a43
--- /dev/null
+++ b/src/com/android/contacts/common/util/DeviceLocalContactsFilterProvider.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2016 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.contacts.common.util;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.LoaderManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.support.annotation.Keep;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.test.NeededForReflection;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Get filters for device local accounts. These are "accounts" that have contacts associated
+ * with them but are not returned by AccountManager. Any other account will be displayed
+ * automatically so we don't worry about it.
+ */
+public class DeviceLocalContactsFilterProvider
+        implements LoaderManager.LoaderCallbacks<Cursor> {
+
+    public static String[] PROJECTION = new String[] {
+            ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE
+    };
+
+    private static final int COL_NAME = 0;
+    private static final int COL_TYPE = 1;
+
+    private final Context mContext;
+    private final DeviceAccountFilter mAccountTypeFilter;
+
+    private String[] mKnownAccountTypes;
+
+    private List<ContactListFilter> mDeviceFilters = Collections.emptyList();
+
+    public DeviceLocalContactsFilterProvider(Context context,
+            DeviceAccountFilter accountTypeFilter) {
+        mContext = context;
+        mAccountTypeFilter = accountTypeFilter;
+    }
+
+    private ContactListFilter createFilterForAccount(Account account) {
+        return new ContactListFilter(ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS,
+                account.type, account.name, null, null);
+    }
+
+    public List<ContactListFilter> getListFilters() {
+        return mDeviceFilters;
+    }
+
+    @Override
+    public CursorLoader onCreateLoader(int i, Bundle bundle) {
+        if (mKnownAccountTypes == null) {
+            initKnownAccountTypes();
+        }
+        return new CursorLoader(mContext, getUri(), PROJECTION, getSelection(),
+                getSelectionArgs(), null);
+    }
+
+
+    private List<ContactListFilter> createFiltersFromResults(Cursor cursor) {
+        final Set<Account> accounts = new HashSet<>();
+        boolean hasNullType = false;
+
+        while (cursor.moveToNext()) {
+            final String name = cursor.getString(COL_NAME);
+            final String type = cursor.getString(COL_TYPE);
+            // The case where where only one of the columns is null isn't handled specifically.
+            if (mAccountTypeFilter.isDeviceAccountType(type)) {
+                if (name != null && type != null) {
+                    accounts.add(new Account(name, type));
+                } else {
+                    hasNullType = true;
+                }
+            }
+        }
+
+        final List<ContactListFilter> result = new ArrayList<>(accounts.size());
+        if (hasNullType) {
+            result.add(new ContactListFilter(ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS,
+                    null, null, null, null));
+        }
+        for (Account account : accounts) {
+            result.add(createFilterForAccount(account));
+        }
+        return result;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+        if (cursor == null) return;
+        mDeviceFilters = createFiltersFromResults(cursor);
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+    }
+
+    @Keep
+    @VisibleForTesting
+    public void setKnownAccountTypes(String... accountTypes) {
+        mKnownAccountTypes = accountTypes;
+    }
+
+    private void initKnownAccountTypes() {
+        final AccountManager accountManager = (AccountManager) mContext
+                .getSystemService(Context.ACCOUNT_SERVICE);
+        final Set<String> knownTypes = new HashSet<>();
+        final Account[] accounts = accountManager.getAccounts();
+        for (Account account : accounts) {
+            if (ContentResolver.getIsSyncable(account, ContactsContract.AUTHORITY) > 0) {
+                knownTypes.add(account.type);
+            }
+        }
+        mKnownAccountTypes = knownTypes.toArray(new String[knownTypes.size()]);
+    }
+
+    private Uri getUri() {
+        final Uri.Builder builder = ContactsContract.RawContacts.CONTENT_URI.buildUpon();
+        if (mKnownAccountTypes == null || mKnownAccountTypes.length == 0) {
+            builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, "1");
+        }
+        return builder.build();
+    }
+
+    private String getSelection() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(ContactsContract.RawContacts.DELETED).append(" =0 AND (")
+                .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL");
+        if (mKnownAccountTypes == null || mKnownAccountTypes.length == 0) {
+            return sb.append(')').toString();
+        }
+        sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN (");
+        for (String ignored : mKnownAccountTypes) {
+            sb.append("?,");
+        }
+        // Remove trailing ','
+        sb.deleteCharAt(sb.length() - 1).append(')').append(')');
+
+        return sb.toString();
+    }
+
+    private String[] getSelectionArgs() {
+        return mKnownAccountTypes;
+    }
+}
diff --git a/src/com/android/contacts/interactions/AccountFiltersFragment.java b/src/com/android/contacts/interactions/AccountFiltersFragment.java
index 7836c19..b2b21f7 100644
--- a/src/com/android/contacts/interactions/AccountFiltersFragment.java
+++ b/src/com/android/contacts/interactions/AccountFiltersFragment.java
@@ -19,11 +19,16 @@
 import android.app.Fragment;
 import android.app.LoaderManager;
 import android.content.Loader;
+import android.database.Cursor;
 import android.os.Bundle;
 
 import com.android.contacts.common.list.ContactListFilter;
 import com.android.contacts.common.util.AccountFilterUtil;
+import com.android.contacts.common.util.DeviceLocalContactsFilterProvider;
+import com.android.contactsbind.ObjectFactory;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -32,6 +37,7 @@
 public class AccountFiltersFragment extends Fragment {
 
     private static final int LOADER_FILTERS = 1;
+    private static final int LOADER_DEVICE_LOCAL_CONTACTS = 3;
 
     /**
      * Callbacks for hosts of the {@link AccountFiltersFragment}.
@@ -44,6 +50,8 @@
         void onFiltersLoaded(List<ContactListFilter> accountFilterItems);
     }
 
+    private LoaderManager.LoaderCallbacks<Cursor> mDeviceLocalLoaderListener;
+
     private final LoaderManager.LoaderCallbacks<List<ContactListFilter>> mFiltersLoaderListener =
             new LoaderManager.LoaderCallbacks<List<ContactListFilter>> () {
                 @Override
@@ -54,24 +62,56 @@
                 @Override
                 public void onLoadFinished(
                         Loader<List<ContactListFilter>> loader, List<ContactListFilter> data) {
-                    if (mListener != null) {
-                        mListener.onFiltersLoaded(data);
+                    if (data == null) {
+                        mLoadedFilters = Collections.emptyList();
+                    } else {
+                        mLoadedFilters = data;
                     }
+                    notifyWithCurrentFilters();
                 }
 
                 public void onLoaderReset(Loader<List<ContactListFilter>> loader) {
                 }
             };
 
+
+    private List<ContactListFilter> mLoadedFilters = null;
+    private List<ContactListFilter> mDeviceLocalFilters = null;
     private AccountFiltersListener mListener;
 
     @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mDeviceLocalLoaderListener = new DeviceLocalContactsFilterProvider(getActivity(),
+                ObjectFactory.getDeviceAccountFilter(getActivity())) {
+            @Override
+            public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+                super.onLoadFinished(loader, data);
+                mDeviceLocalFilters = getListFilters();
+                notifyWithCurrentFilters();
+            }
+        };
+    }
+
+    @Override
     public void onStart() {
         getLoaderManager().initLoader(LOADER_FILTERS, null, mFiltersLoaderListener);
+        getLoaderManager().initLoader(LOADER_DEVICE_LOCAL_CONTACTS, null,
+                mDeviceLocalLoaderListener);
+
         super.onStart();
     }
 
     public void setListener(AccountFiltersListener listener) {
         mListener = listener;
     }
+
+    private void notifyWithCurrentFilters() {
+        if (mListener == null || mLoadedFilters == null || mDeviceLocalFilters == null) return;
+
+        final List<ContactListFilter> result = new ArrayList<>(mLoadedFilters);
+        result.addAll(mDeviceLocalFilters);
+        mListener.onFiltersLoaded(result);
+    }
 }
diff --git a/tests/src/com/android/contacts/common/util/DeviceLocalContactsFilterProviderTests.java b/tests/src/com/android/contacts/common/util/DeviceLocalContactsFilterProviderTests.java
new file mode 100644
index 0000000..d776ab8
--- /dev/null
+++ b/tests/src/com/android/contacts/common/util/DeviceLocalContactsFilterProviderTests.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2016 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.contacts.common.util;
+
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.RawContacts;
+import android.support.annotation.Nullable;
+import android.test.LoaderTestCase;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.common.test.mocks.MockContentProvider;
+
+import org.mockito.Mockito;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.mockito.Mockito.when;
+
+@SmallTest
+public class DeviceLocalContactsFilterProviderTests extends LoaderTestCase {
+
+    // Basic smoke test that just checks that it doesn't throw when loading from CP2. We don't
+    // care what CP2 actually contains for this.
+    public void testShouldNotCrash() {
+        final DeviceLocalContactsFilterProvider sut = new DeviceLocalContactsFilterProvider(
+                getContext(), DeviceAccountFilter.ONLY_NULL);
+        final CursorLoader loader = sut.onCreateLoader(0, null);
+        getLoaderResultSynchronously(loader);
+        // We didn't throw so it passed
+    }
+
+    public void testCreatesNoFiltersIfNoRawContactsHaveDeviceAccountType() {
+        final DeviceLocalContactsFilterProvider sut = createWithFilterAndLoaderResult(
+                DeviceAccountFilter.ONLY_NULL, queryResult(
+                        "user", "com.example",
+                        "user", "com.example",
+                        "user", "com.example"));
+        sut.setKnownAccountTypes("com.example");
+
+        doLoad(sut);
+
+        assertEquals(0, sut.getListFilters().size());
+    }
+
+    public void testCreatesOneFilterForDeviceAccount() {
+        final DeviceLocalContactsFilterProvider sut = createWithFilterAndLoaderResult(
+                DeviceAccountFilter.ONLY_NULL, queryResult(
+                        "user", "com.example",
+                        "user", "com.example",
+                        null, null,
+                        "user", "com.example",
+                        null, null));
+        sut.setKnownAccountTypes("com.example");
+
+        doLoad(sut);
+
+        assertEquals(1, sut.getListFilters().size());
+        assertEquals(ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS,
+                sut.getListFilters().get(0).filterType);
+    }
+
+    public void testCreatesOneFilterForEachDeviceAccount() {
+         final DeviceLocalContactsFilterProvider sut = createWithFilterAndLoaderResult(
+                 filterAllowing(null, "vnd.sec.contact.phone", "vnd.sec.contact.sim"), queryResult(
+                         "sim_account", "vnd.sec.contact.sim",
+                         "user", "com.example",
+                         "user", "com.example",
+                         "phone_account", "vnd.sec.contact.phone",
+                         null, null,
+                         "phone_account", "vnd.sec.contact.phone",
+                         "user", "com.example",
+                         null, null,
+                         "sim_account", "vnd.sec.contact.sim",
+                         "sim_account_2", "vnd.sec.contact.sim"
+                 ));
+        sut.setKnownAccountTypes("com.example");
+
+        doLoad(sut);
+
+        assertEquals(4, sut.getListFilters().size());
+    }
+
+    public void testFilterIsUpdatedWhenLoaderReloads() {
+        final FakeContactsProvider provider = new FakeContactsProvider();
+        final DeviceLocalContactsFilterProvider sut = new DeviceLocalContactsFilterProvider(
+                createStubContextWithContactsProvider(provider), DeviceAccountFilter.ONLY_NULL);
+        sut.setKnownAccountTypes("com.example");
+
+        provider.setNextQueryResult(queryResult(
+                null, null,
+                "user", "com.example",
+                "user", "com.example"
+        ));
+        doLoad(sut);
+
+        assertFalse(sut.getListFilters().isEmpty());
+
+        provider.setNextQueryResult(queryResult(
+                "user", "com.example",
+                "user", "com.example"
+        ));
+        doLoad(sut);
+
+        assertTrue(sut.getListFilters().isEmpty());
+    }
+
+    public void testDoesNotCreateFiltersForKnownAccounts() {
+        final DeviceLocalContactsFilterProvider sut = new DeviceLocalContactsFilterProvider(
+                getContext(), DeviceAccountFilter.ONLY_NULL);
+        sut.setKnownAccountTypes("com.example", "maybe_syncable_device_account_type");
+
+        final CursorLoader loader = sut.onCreateLoader(0, null);
+
+        // The filtering is done at the DB level rather than in the code so just verify that
+        // selection is about right.
+        assertTrue("Loader selection is wrong", loader.getSelection().contains("NOT IN (?,?)"));
+        assertEquals("com.example", loader.getSelectionArgs()[0]);
+        assertEquals("maybe_syncable_device_account_type", loader.getSelectionArgs()[1]);
+    }
+
+    private void doLoad(DeviceLocalContactsFilterProvider loaderCallbacks) {
+        final CursorLoader loader = loaderCallbacks.onCreateLoader(0, null);
+        final Cursor cursor = getLoaderResultSynchronously(loader);
+        loaderCallbacks.onLoadFinished(loader, cursor);
+    }
+
+    private DeviceLocalContactsFilterProvider createWithFilterAndLoaderResult(
+            DeviceAccountFilter filter, Cursor cursor) {
+        final DeviceLocalContactsFilterProvider result = new DeviceLocalContactsFilterProvider(
+                createStubContextWithContentQueryResult(cursor), filter);
+        return result;
+    }
+
+    private Context createStubContextWithContentQueryResult(final Cursor cursor) {
+        return createStubContextWithContactsProvider(new FakeContactsProvider(cursor));
+    }
+
+    private Context createStubContextWithContactsProvider(ContentProvider contactsProvider) {
+        final MockContentResolver resolver = new MockContentResolver();
+        resolver.addProvider(ContactsContract.AUTHORITY, contactsProvider);
+
+        final Context context = Mockito.mock(MockContext.class);
+        when(context.getContentResolver()).thenReturn(resolver);
+
+        // The loader pulls out the application context instead of usign the context directly
+        when(context.getApplicationContext()).thenReturn(context);
+
+        return context;
+    }
+
+    private Cursor queryResult(String... typeNamePairs) {
+        final MatrixCursor cursor = new MatrixCursor(new String[]
+                { RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE });
+        for (int i = 0; i < typeNamePairs.length; i += 2) {
+            cursor.newRow().add(typeNamePairs[i]).add(typeNamePairs[i+1]);
+        }
+        return cursor;
+    }
+
+    private DeviceAccountFilter filterAllowing(String... accountTypes) {
+        final Set<String> allowed = new HashSet<>(Arrays.asList(accountTypes));
+        return new DeviceAccountFilter() {
+            @Override
+            public boolean isDeviceAccountType(String accountType) {
+                return allowed.contains(accountType);
+            }
+        };
+    }
+
+    private static class FakeContactsProvider extends MockContentProvider {
+        public Cursor mNextQueryResult;
+
+        public FakeContactsProvider() {}
+
+        public FakeContactsProvider(Cursor result) {
+            mNextQueryResult = result;
+        }
+
+        public void setNextQueryResult(Cursor result) {
+            mNextQueryResult = result;
+        }
+
+        @Override
+        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+                String sortOrder) {
+            return query(uri, projection, selection, selectionArgs, sortOrder, null);
+        }
+
+        @Nullable
+        @Override
+        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+                String sortOrder, CancellationSignal cancellationSignal) {
+            return mNextQueryResult;
+        }
+    }
+}