Merge "Fix long lines (>100) in ContactsProvider"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ea70f64..85fa07c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -54,6 +54,15 @@
             android:writePermission="android.permission.WRITE_CALL_LOG">
         </provider>
 
+        <provider android:name="ShadowCallLogProvider"
+                  android:authorities="call_log_shadow"
+                  android:syncable="false" android:multiprocess="false"
+                  android:exported="true"
+                  android:encryptionAware="true"
+                  android:readPermission="android.permission.MANAGE_USERS"
+                  android:writePermission="android.permission.MANAGE_USERS">
+        </provider>
+
         <provider android:name="VoicemailContentProvider"
             android:authorities="com.android.voicemail"
             android:syncable="false" android:multiprocess="false"
diff --git a/src/com/android/providers/contacts/CallLogDatabaseHelper.java b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
index 726d99c..2c03a79 100644
--- a/src/com/android/providers/contacts/CallLogDatabaseHelper.java
+++ b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
@@ -42,8 +42,13 @@
 
     private static final String DATABASE_NAME = "calllog.db";
 
+    private static final String SHADOW_DATABASE_NAME = "calllog_shadow.db";
+
     private static CallLogDatabaseHelper sInstance;
 
+    /** Instance for the "shadow" provider. */
+    private static CallLogDatabaseHelper sInstanceForShadow;
+
     private final Context mContext;
 
     private final OpenHelper mOpenHelper;
@@ -55,6 +60,7 @@
 
     public interface DbProperties {
         String CALL_LOG_LAST_SYNCED = "call_log_last_synced";
+        String CALL_LOG_LAST_SYNCED_FOR_SHADOW = "call_log_last_synced_for_shadow";
         String DATA_MIGRATED = "migrated";
     }
 
@@ -186,6 +192,15 @@
         return sInstance;
     }
 
+    public static synchronized CallLogDatabaseHelper getInstanceForShadow(Context context) {
+        if (sInstanceForShadow == null) {
+            // Shadow provider is always encryption-aware.
+            sInstanceForShadow = new CallLogDatabaseHelper(
+                    context.createDeviceEncryptedStorageContext(), SHADOW_DATABASE_NAME);
+        }
+        return sInstanceForShadow;
+    }
+
     public SQLiteDatabase getReadableDatabase() {
         return mOpenHelper.getReadableDatabase();
     }
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index ce86cf7..d364dd3 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -24,6 +24,7 @@
 
 import android.app.AppOpsManager;
 import android.content.ContentProvider;
+import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
@@ -33,6 +34,7 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
@@ -52,6 +54,7 @@
 import com.android.providers.contacts.util.SelectionBuilder;
 import com.android.providers.contacts.util.UserUtils;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -62,6 +65,8 @@
 public class CallLogProvider extends ContentProvider {
     private static final String TAG = CallLogProvider.class.getSimpleName();
 
+    public static final boolean VERBOSE_LOGGING = false; // DO NOT SUBMIT WITH TRUE
+
     private static final int BACKGROUND_TASK_INITIALIZE = 0;
     private static final int BACKGROUND_TASK_ADJUST_PHONE_ACCOUNT = 1;
 
@@ -109,6 +114,9 @@
         sURIMatcher.addURI(CallLog.AUTHORITY, "calls", CALLS);
         sURIMatcher.addURI(CallLog.AUTHORITY, "calls/#", CALLS_ID);
         sURIMatcher.addURI(CallLog.AUTHORITY, "calls/filter/*", CALLS_FILTER);
+
+        // Shadow provider only supports "/calls".
+        sURIMatcher.addURI(CallLog.SHADOW_AUTHORITY, "calls", CALLS);
     }
 
     private static final HashMap<String, String> sCallsProjectionMap;
@@ -157,11 +165,19 @@
     private VoicemailPermissions mVoicemailPermissions;
     private CallLogInsertionHelper mCallLogInsertionHelper;
 
+    protected boolean isShadow() {
+        return false;
+    }
+
+    protected final String getProviderName() {
+        return this.getClass().getSimpleName();
+    }
+
     @Override
     public boolean onCreate() {
         setAppOps(AppOpsManager.OP_READ_CALL_LOG, AppOpsManager.OP_WRITE_CALL_LOG);
         if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
-            Log.d(Constants.PERFORMANCE_TAG, "CallLogProvider.onCreate start");
+            Log.d(Constants.PERFORMANCE_TAG, getProviderName() + ".onCreate start");
         }
         final Context context = getContext();
         mDbHelper = getDatabaseHelper(context);
@@ -171,7 +187,7 @@
         mVoicemailPermissions = new VoicemailPermissions(context);
         mCallLogInsertionHelper = createCallLogInsertionHelper(context);
 
-        mBackgroundThread = new HandlerThread("CallLogProviderWorker",
+        mBackgroundThread = new HandlerThread(getProviderName() + "Worker",
                 Process.THREAD_PRIORITY_BACKGROUND);
         mBackgroundThread.start();
         mBackgroundHandler = new Handler(mBackgroundThread.getLooper()) {
@@ -186,7 +202,7 @@
         scheduleBackgroundTask(BACKGROUND_TASK_INITIALIZE, null);
 
         if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
-            Log.d(Constants.PERFORMANCE_TAG, "CallLogProvider.onCreate finish");
+            Log.d(Constants.PERFORMANCE_TAG, getProviderName() + ".onCreate finish");
         }
         return true;
     }
@@ -196,7 +212,6 @@
         return DefaultCallLogInsertionHelper.getInstance(context);
     }
 
-    @VisibleForTesting
     protected CallLogDatabaseHelper getDatabaseHelper(final Context context) {
         return CallLogDatabaseHelper.getInstance(context);
     }
@@ -204,6 +219,12 @@
     @Override
     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
             String sortOrder) {
+        if (VERBOSE_LOGGING) {
+            Log.v(TAG, "query: uri=" + uri + "  projection=" + Arrays.toString(projection) +
+                    "  selection=[" + selection + "]  args=" + Arrays.toString(selectionArgs) +
+                    "  order=[" + sortOrder + "] CPID=" + Binder.getCallingPid() +
+                    " User=" + UserUtils.getCurrentUserHandle(getContext()));
+        }
         waitForAccess(mReadAccessLatch);
         final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
         qb.setTables(Tables.CALLS);
@@ -301,6 +322,10 @@
 
     @Override
     public Uri insert(Uri uri, ContentValues values) {
+        if (VERBOSE_LOGGING) {
+            Log.v(TAG, "insert: uri=" + uri + "  values=[" + values + "]" +
+                    " CPID=" + Binder.getCallingPid());
+        }
         waitForAccess(mReadAccessLatch);
         checkForSupportedColumns(sCallsProjectionMap, values);
         // Inserting a voicemail record through call_log requires the voicemail
@@ -328,6 +353,12 @@
 
     @Override
     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        if (VERBOSE_LOGGING) {
+            Log.v(TAG, "update: uri=" + uri +
+                    "  selection=[" + selection + "]  args=" + Arrays.toString(selectionArgs) +
+                    "  values=[" + values + "] CPID=" + Binder.getCallingPid() +
+                    " User=" + UserUtils.getCurrentUserHandle(getContext()));
+        }
         waitForAccess(mReadAccessLatch);
         checkForSupportedColumns(sCallsProjectionMap, values);
         // Request that involves changing record type to voicemail requires the
@@ -359,6 +390,12 @@
 
     @Override
     public int delete(Uri uri, String selection, String[] selectionArgs) {
+        if (VERBOSE_LOGGING) {
+            Log.v(TAG, "delete: uri=" + uri +
+                    "  selection=[" + selection + "]  args=" + Arrays.toString(selectionArgs) +
+                    " CPID=" + Binder.getCallingPid() +
+                    " User=" + UserUtils.getCurrentUserHandle(getContext()));
+        }
         waitForAccess(mReadAccessLatch);
         SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
         checkVoicemailPermissionAndAddRestriction(uri, selectionBuilder, false /*isQuery*/);
@@ -367,6 +404,8 @@
         final int matchedUriId = sURIMatcher.match(uri);
         switch (matchedUriId) {
             case CALLS:
+                // TODO: Special case - We may want to forward the delete request on user 0 to the
+                // shadow provider too.
                 return getDatabaseModifier(db).delete(Tables.CALLS,
                         selectionBuilder.build(), selectionArgs);
             default:
@@ -455,37 +494,76 @@
     }
 
     /**
-     * Syncs any unique call log entries that have been inserted into the primary user's call log
-     * since the last time the last sync occurred.
+     * Sync all calllog entries that were inserted
      */
-    private void syncEntriesFromPrimaryUser(UserManager userManager) {
-        final int userHandle = userManager.getUserHandle();
+    private void syncEntries() {
+        if (isShadow()) {
+            return; // It's the shadow provider itself.  No copying.
+        }
+
+        final UserManager userManager = UserUtils.getUserManager(getContext());
+
         // TODO: http://b/24944959
-        if (userHandle == UserHandle.USER_SYSTEM
-            || userManager.getUserInfo(userHandle).isManagedProfile()) {
+        if (!Calls.shouldHaveSharedCallLogEntries(getContext(), userManager,
+                userManager.getUserHandle())) {
             return;
         }
 
-        final long lastSyncTime = getLastSyncTime();
-        final Uri uri = ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI,
-                UserHandle.USER_SYSTEM);
-        final Cursor cursor = getContext().getContentResolver().query(
+        final int myUserId = userManager.getUserHandle();
+
+        // See the comment in Calls.addCall() for the logic.
+
+        if (userManager.isSystemUser()) {
+            // If it's the system user, just copy from shadow.
+            syncEntriesFrom(UserHandle.USER_SYSTEM, /* sourceIsShadow = */ true,
+                    /* forAllUsersOnly =*/ false);
+        } else {
+            // Otherwise, copy from system's real provider, as well as self's shadow.
+            syncEntriesFrom(UserHandle.USER_SYSTEM, /* sourceIsShadow = */ false,
+                    /* forAllUsersOnly =*/ true);
+            syncEntriesFrom(myUserId, /* sourceIsShadow = */ true,
+                    /* forAllUsersOnly =*/ false);
+        }
+    }
+
+    private void syncEntriesFrom(int sourceUserId, boolean sourceIsShadow,
+            boolean forAllUsersOnly) {
+
+        final Uri sourceUri = sourceIsShadow ? Calls.SHADOW_CONTENT_URI : Calls.CONTENT_URI;
+
+        final long lastSyncTime = getLastSyncTime(sourceIsShadow);
+
+        final Uri uri = ContentProvider.maybeAddUserId(sourceUri, sourceUserId);
+        final long newestTimeStamp;
+        final ContentResolver cr = getContext().getContentResolver();
+
+        final StringBuilder selection = new StringBuilder();
+
+        selection.append(
+                "(" + EXCLUDE_VOICEMAIL_SELECTION + ") AND (" + MORE_RECENT_THAN_SELECTION + ")");
+
+        if (forAllUsersOnly) {
+            selection.append(" AND (" + Calls.ADD_FOR_ALL_USERS + "=1)");
+        }
+
+        final Cursor cursor = cr.query(
                 uri,
                 CALL_LOG_SYNC_PROJECTION,
-                EXCLUDE_VOICEMAIL_SELECTION + " AND " + MORE_RECENT_THAN_SELECTION,
+                selection.toString(),
                 new String[] {String.valueOf(lastSyncTime)},
-                Calls.DATE + " DESC");
+                Calls.DATE + " ASC");
         if (cursor == null) {
             return;
         }
         try {
-            final long lastSyncedEntryTime = copyEntriesFromCursor(cursor);
-            if (lastSyncedEntryTime > lastSyncTime) {
-                setLastTimeSynced(lastSyncedEntryTime);
-            }
+            newestTimeStamp = copyEntriesFromCursor(cursor, lastSyncTime, sourceIsShadow);
         } finally {
             cursor.close();
         }
+        if (sourceIsShadow) {
+            // delete all entries in shadow.
+            cr.delete(uri, Calls.DATE + "<= ?", new String[] {String.valueOf(newestTimeStamp)});
+        }
     }
 
     /**
@@ -532,12 +610,10 @@
 
     /**
      * @param cursor to copy call log entries from
-     *
-     * @return the timestamp of the last synced entry.
      */
     @VisibleForTesting
-    long copyEntriesFromCursor(Cursor cursor) {
-        long lastSynced = 0;
+    long copyEntriesFromCursor(Cursor cursor, long lastSyncTime, boolean forShadow) {
+        long latestTimestamp = 0;
         final ContentValues values = new ContentValues();
         final SQLiteDatabase db = mDbHelper.getWritableDatabase();
         db.beginTransaction();
@@ -548,11 +624,6 @@
                 values.clear();
                 DatabaseUtils.cursorRowToContentValues(cursor, values);
 
-                final boolean addForAllUsers = values.getAsInteger(Calls.ADD_FOR_ALL_USERS) == 1;
-                if (!addForAllUsers) {
-                    continue;
-                }
-
                 final String startTime = values.getAsString(Calls.DATE);
                 final String number = values.getAsString(Calls.NUMBER);
 
@@ -562,7 +633,7 @@
 
                 if (cursor.isLast()) {
                     try {
-                        lastSynced = Long.valueOf(startTime);
+                        latestTimestamp = Long.valueOf(startTime);
                     } catch (NumberFormatException e) {
                         Log.e(TAG, "Call log entry does not contain valid start time: "
                                 + startTime);
@@ -580,23 +651,35 @@
 
                 db.insert(Tables.CALLS, null, values);
             }
+
+            if (latestTimestamp > lastSyncTime) {
+                setLastTimeSynced(latestTimestamp, forShadow);
+            }
+
             db.setTransactionSuccessful();
         } finally {
             db.endTransaction();
         }
-        return lastSynced;
+        return latestTimestamp;
     }
 
-    private long getLastSyncTime() {
+    private static String getLastSyncTimePropertyName(boolean forShadow) {
+        return forShadow
+                ? DbProperties.CALL_LOG_LAST_SYNCED_FOR_SHADOW
+                : DbProperties.CALL_LOG_LAST_SYNCED;
+    }
+
+    @VisibleForTesting
+    long getLastSyncTime(boolean forShadow) {
         try {
-            return Long.valueOf(mDbHelper.getProperty(DbProperties.CALL_LOG_LAST_SYNCED, "0"));
+            return Long.valueOf(mDbHelper.getProperty(getLastSyncTimePropertyName(forShadow), "0"));
         } catch (NumberFormatException e) {
             return 0;
         }
     }
 
-    private void setLastTimeSynced(long time) {
-        mDbHelper.setProperty(DbProperties.CALL_LOG_LAST_SYNCED, String.valueOf(time));
+    private void setLastTimeSynced(long time, boolean forShadow) {
+        mDbHelper.setProperty(getLastSyncTimePropertyName(forShadow), String.valueOf(time));
     }
 
     private static void waitForAccess(CountDownLatch latch) {
@@ -621,14 +704,7 @@
     private void performBackgroundTask(int task, Object arg) {
         if (task == BACKGROUND_TASK_INITIALIZE) {
             try {
-                final Context context = getContext();
-                if (context != null) {
-                    final UserManager userManager = UserUtils.getUserManager(context);
-                    if (userManager != null &&
-                            !userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS)) {
-                        syncEntriesFromPrimaryUser(userManager);
-                    }
-                }
+                syncEntries();
             } finally {
                 mReadAccessLatch.countDown();
                 mReadAccessLatch = null;
@@ -636,6 +712,5 @@
         } else if (task == BACKGROUND_TASK_ADJUST_PHONE_ACCOUNT) {
             adjustForNewPhoneAccountInternal((PhoneAccountHandle) arg);
         }
-
     }
 }
diff --git a/src/com/android/providers/contacts/ContactDirectoryManager.java b/src/com/android/providers/contacts/ContactDirectoryManager.java
index b7039a2..447ab28 100644
--- a/src/com/android/providers/contacts/ContactDirectoryManager.java
+++ b/src/com/android/providers/contacts/ContactDirectoryManager.java
@@ -175,7 +175,7 @@
      */
     public void scanAllPackages(boolean rescan) {
         if (rescan || !areTypeResourceIdsValid()) {
-            getDbHelper().setProperty(DbProperties.DIRECTORY_SCAN_COMPLETE, "0");
+            getDbHelper().clearDirectoryScanComplete();
         }
 
         scanAllPackagesIfNeeded();
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 6bc42a4..dc301f8 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -5629,6 +5629,10 @@
         PropertyUtils.setProperty(getWritableDatabase(), key, value);
     }
 
+    public void clearDirectoryScanComplete() {
+        setProperty(DbProperties.DIRECTORY_SCAN_COMPLETE, "0");
+    }
+
     /**
      * Test if the given column appears in the given projection.
      */
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 1519a83..7f219eb 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -1770,7 +1770,6 @@
                 initForDefaultLocale();
                 mReadAccessLatch.countDown();
                 mReadAccessLatch = null;
-                updateDirectoriesInBackground(true);
                 break;
             }
 
@@ -6946,6 +6945,9 @@
                     return new MatrixCursor(outputProjection);
                 }
                 final Uri remoteUri = maybeAddUserId(RawContactsEntity.CONTENT_URI, corpUserId);
+                // This method is used by Bluetooth Contacts Sharing only, it uses enterprise
+                // contact id to get work contacts info, so work profile should be available at this
+                // moment.
                 return getContext().getContentResolver().query(remoteUri, projection, selection,
                         selectionArgs, sortOrder);
             }
@@ -7012,6 +7014,12 @@
                             corpUserId);
                     final Cursor cursor = getContext().getContentResolver().query(remoteUri,
                             projection, selection, selectionArgs, sortOrder);
+                    if (cursor == null) {
+                        // Work profile is not available yet
+                        final String[] outputProjection = (projection != null) ? projection
+                                : sDirectoryProjectionMap.getColumnNames();
+                        return new MatrixCursor(outputProjection);
+                    }
                     return rewriteCorpDirectories(cursor);
                 } else {
                     // As it is not an enterprise directory id, fall back to original API
@@ -8957,6 +8965,7 @@
                     String.valueOf(directoryId - Directory.ENTERPRISE_DIRECTORY_ID_BASE));
             addQueryParametersFromUri(builder, uri, MODIFIED_KEY_SET_FOR_ENTERPRISE_FILTER);
 
+            // If work profile is not available, it will throw FileNotFoundException
             remoteUri = maybeAddUserId(builder.build(), corpUserId);
         } else {
             final DirectoryInfo directoryInfo = getDirectoryAuthority(directory);
@@ -9009,6 +9018,7 @@
         }
         // Convert the URI into:
         // content://USER@com.android.contacts/contacts_corp/ID/{photo,display_photo}
+        // If work profile is not available, it will throw FileNotFoundException
         final Uri corpUri = maybeAddUserId(
                 ContentUris.appendId(Contacts.CONTENT_URI.buildUpon(), contactId)
                         .appendPath(displayPhoto ?
diff --git a/src/com/android/providers/contacts/ContactsUpgradeReceiver.java b/src/com/android/providers/contacts/ContactsUpgradeReceiver.java
index 99727bc..57c0cd0 100644
--- a/src/com/android/providers/contacts/ContactsUpgradeReceiver.java
+++ b/src/com/android/providers/contacts/ContactsUpgradeReceiver.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -42,6 +43,7 @@
     static final String TAG = "ContactsUpgradeReceiver";
     static final String PREF_DB_VERSION = "db_version";
     static final String PREF_ICU_VERSION = "icu_version";
+    static final String PREF_OS_VERSION = "os_version";
 
     @Override
     public void onReceive(Context context, Intent intent) {
@@ -52,14 +54,19 @@
             long startTime = System.currentTimeMillis();
 
             // Lookup the last known database version
-            SharedPreferences prefs = context.getSharedPreferences(TAG, Context.MODE_PRIVATE);
-            int prefDbVersion = prefs.getInt(PREF_DB_VERSION, 0);
+            final SharedPreferences prefs = context.getSharedPreferences(TAG, Context.MODE_PRIVATE);
+            final int prefDbVersion = prefs.getInt(PREF_DB_VERSION, 0);
+
             final String curIcuVersion = ICU.getIcuVersion();
+            final String curOsVersion = getOsVersionString();
+
             final String prefIcuVersion = prefs.getString(PREF_ICU_VERSION, "");
+            final String prefOsVersion = prefs.getString(PREF_OS_VERSION, "");
 
             // If the version is old go ahead and attempt to create or upgrade the database.
             if (prefDbVersion != ContactsDatabaseHelper.DATABASE_VERSION ||
-                    !prefIcuVersion.equals(curIcuVersion)) {
+                    !prefIcuVersion.equals(curIcuVersion) ||
+                    !prefOsVersion.equals(curOsVersion)) {
                 // Store the current version so this receiver isn't run again until the database
                 // version number changes. This is intentionally done even before the upgrade path
                 // is attempted to be conservative. If the upgrade fails for some reason and we
@@ -67,6 +74,7 @@
                 SharedPreferences.Editor editor = prefs.edit();
                 editor.putInt(PREF_DB_VERSION, ContactsDatabaseHelper.DATABASE_VERSION);
                 editor.putString(PREF_ICU_VERSION, curIcuVersion);
+                editor.putString(PREF_OS_VERSION, curOsVersion);
                 editor.commit();
 
                 // Ask for a reference to the database to force the helper to either
@@ -79,8 +87,11 @@
                 Log.i(TAG, "Creating or opening contacts database");
 
                 helper.getWritableDatabase();
+                helper.clearDirectoryScanComplete();
+
                 profileHelper.getWritableDatabase();
                 calllogHelper.getWritableDatabase();
+
                 ContactsProvider2.updateLocaleOffline(context, helper, profileHelper);
 
                 // Log the total time taken for the receiver to perform the operation
@@ -96,4 +107,8 @@
                     PackageManager.DONT_KILL_APP);
         }
     }
+
+    private static String getOsVersionString() {
+        return Build.ID;
+    }
 }
diff --git a/src/com/android/providers/contacts/ShadowCallLogProvider.java b/src/com/android/providers/contacts/ShadowCallLogProvider.java
new file mode 100644
index 0000000..2cacdc2
--- /dev/null
+++ b/src/com/android/providers/contacts/ShadowCallLogProvider.java
@@ -0,0 +1,30 @@
+/*
+ * 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.providers.contacts;
+
+import android.content.Context;
+
+public class ShadowCallLogProvider extends CallLogProvider {
+    protected CallLogDatabaseHelper getDatabaseHelper(final Context context) {
+        return CallLogDatabaseHelper.getInstanceForShadow(context);
+    }
+
+    @Override
+    protected boolean isShadow() {
+        return true;
+    }
+}
diff --git a/tests/src/com/android/providers/contacts/CallLogProviderTest.java b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
index fb771a3..590a0a3 100644
--- a/tests/src/com/android/providers/contacts/CallLogProviderTest.java
+++ b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
@@ -18,6 +18,7 @@
 
 import com.android.internal.telephony.CallerInfo;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.providers.contacts.CallLogDatabaseHelper.DbProperties;
 import com.android.providers.contacts.testutil.CommonDatabaseUtils;
 
 import android.content.ComponentName;
@@ -187,6 +188,8 @@
         Uri uri = Calls.addCall(ci, getMockContext(), "1-800-263-7643",
                 PhoneConstants.PRESENTATION_ALLOWED, Calls.OUTGOING_TYPE, 0, subscription, 2000,
                 40, null);
+        assertNotNull(uri);
+        assertEquals("0@" + CallLog.AUTHORITY, uri.getAuthority());
 
         ContentValues values = new ContentValues();
         values.put(Calls.TYPE, Calls.OUTGOING_TYPE);
@@ -398,27 +401,33 @@
         mResolver.delete(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null);
     }
 
-    public void testCopyEntriesFromCursor_ReturnsMostRecentEntryTimestamp() {
-        assertEquals(10, mCallLogProvider.copyEntriesFromCursor(getTestCallLogCursor()));
-    }
-
     public void testCopyEntriesFromCursor_AllEntriesSyncedWithoutDuplicatesPresent() {
         assertStoredValues(Calls.CONTENT_URI);
-        mCallLogProvider.copyEntriesFromCursor(getTestCallLogCursor());
+
+        assertEquals(10, mCallLogProvider.copyEntriesFromCursor(
+                getTestCallLogCursor(), 5, /* forShadow =*/ true));
+
         assertStoredValues(Calls.CONTENT_URI,
                 getTestCallLogValues(2),
                 getTestCallLogValues(1),
                 getTestCallLogValues(0));
+        assertEquals(10, mCallLogProvider.getLastSyncTime(/* forShadow =*/ true));
+        assertEquals(0, mCallLogProvider.getLastSyncTime(/* forShadow =*/ false));
     }
 
     public void testCopyEntriesFromCursor_DuplicatesIgnoredCorrectly() {
         mResolver.insert(Calls.CONTENT_URI, getTestCallLogValues(1));
         assertStoredValues(Calls.CONTENT_URI, getTestCallLogValues(1));
-        mCallLogProvider.copyEntriesFromCursor(getTestCallLogCursor());
+
+        assertEquals(10, mCallLogProvider.copyEntriesFromCursor(
+                getTestCallLogCursor(), 5, /* forShadow =*/ false));
+
         assertStoredValues(Calls.CONTENT_URI,
                 getTestCallLogValues(2),
                 getTestCallLogValues(1),
                 getTestCallLogValues(0));
+        assertEquals(0, mCallLogProvider.getLastSyncTime(/* forShadow =*/ true));
+        assertEquals(10, mCallLogProvider.getLastSyncTime(/* forShadow =*/ false));
     }
 
     private ContentValues getDefaultValues(int callType) {
diff --git a/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java b/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java
index c5bc6f6..8ad5ca1 100644
--- a/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java
+++ b/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java
@@ -37,6 +37,7 @@
 import android.util.Log;
 
 import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
+
 import com.google.android.collect.Lists;
 
 /**
@@ -173,24 +174,38 @@
 
         mDirectoryManager.scanAllPackages();
 
-        Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
+        Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null,
+                /* order by=*/ Directory.DIRECTORY_AUTHORITY + "," + Directory.ACCOUNT_NAME +
+                "," + Directory._ID );
+
+        TestUtils.dumpCursor(cursor);
         assertEquals(5, cursor.getCount());
 
-        cursor.moveToPosition(2);
+        assertTrue(cursor.moveToPosition(0));
         assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1",
                 "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
                 Directory.PHOTO_SUPPORT_FULL_SIZE_ONLY);
 
-        cursor.moveToNext();
+        assertTrue(cursor.moveToPosition(1));
         assertDirectoryRow(cursor, "test.package1", "authority1", "account-name2", "account-type2",
                 "display-name2", 2, Directory.EXPORT_SUPPORT_ANY_ACCOUNT,
                 Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY, Directory.PHOTO_SUPPORT_THUMBNAIL_ONLY);
 
-        cursor.moveToNext();
+        assertTrue(cursor.moveToPosition(2));
         assertDirectoryRow(cursor, "test.package2", "authority2", "account-name3", "account-type3",
                 "display-name3", 3, Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY,
                 Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL);
 
+        assertTrue(cursor.moveToPosition(3));
+        assertDirectoryRow(cursor, "contactsTestPackage", "com.android.contacts", null, null,
+                null, -1 /* =any */, Directory.EXPORT_SUPPORT_NONE,
+                Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL);
+
+        assertTrue(cursor.moveToPosition(4));
+        assertDirectoryRow(cursor, "contactsTestPackage", "com.android.contacts", null, null,
+                null, -1 /* =any */, Directory.EXPORT_SUPPORT_NONE,
+                Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL);
+
         cursor.close();
     }
 
@@ -623,7 +638,9 @@
         values.put(Directory.ACCOUNT_NAME, accountName);
         values.put(Directory.ACCOUNT_TYPE, accountType);
         values.put(Directory.DISPLAY_NAME, displayName);
-        values.put(Directory.TYPE_RESOURCE_ID, typeResourceId);
+        if (typeResourceId >= 0) {
+            values.put(Directory.TYPE_RESOURCE_ID, typeResourceId);
+        }
         values.put(Directory.EXPORT_SUPPORT, exportSupport);
         values.put(Directory.SHORTCUT_SUPPORT, shortcutSupport);
         values.put(Directory.PHOTO_SUPPORT, photoSupport);
diff --git a/tests/src/com/android/providers/contacts/ContactsActor.java b/tests/src/com/android/providers/contacts/ContactsActor.java
index 1d7931e..fd75e45 100644
--- a/tests/src/com/android/providers/contacts/ContactsActor.java
+++ b/tests/src/com/android/providers/contacts/ContactsActor.java
@@ -275,7 +275,28 @@
 
         resolver = new MockContentResolver();
         context = new RestrictionMockContext(overallContext, packageName, resolver,
-                mGrantedPermissions, mGrantedUriPermissions);
+                mGrantedPermissions, mGrantedUriPermissions) {
+            @Override
+            public Object getSystemService(String name) {
+                if (Context.COUNTRY_DETECTOR.equals(name)) {
+                    return mMockCountryDetector;
+                }
+                if (Context.ACCOUNT_SERVICE.equals(name)) {
+                    return mMockAccountManager;
+                }
+                if (Context.USER_SERVICE.equals(name)) {
+                    return mockUserManager;
+                }
+                // Use overallContext here; super.getSystemService() somehow won't return
+                // DevicePolicyManager.
+                return overallContext.getSystemService(name);
+            }
+
+            @Override
+            public String getSystemServiceName(Class<?> serviceClass) {
+                return overallContext.getSystemServiceName(serviceClass);
+            }
+        };
         this.packageName = packageName;
 
         // Let the Secure class initialize the settings provider, which is done when we first
@@ -357,12 +378,12 @@
         // info shouldn't have it.
         info.authority = stripOutUserIdFromAuthority(authority);
         provider.attachInfoForTesting(providerContext, info);
-        resolver.addProvider(authority, provider);
 
         // In case of LegacyTest, "authority" here is actually multiple authorities.
         // Register all authority here.
         for (String a : authority.split(";")) {
             resolver.addProvider(a, provider);
+            resolver.addProvider("0@" + a, provider);
         }
         return provider;
     }
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index 2f09c1f..d14af72 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -2200,153 +2200,44 @@
         c.close();
     }
 
-    public void testRewriteCorpLookup() {
-        // 19 columns
+    public void testRewriteCorpDirectories() {
+        // 6 columns
         final MatrixCursor c = new MatrixCursor(new String[] {
-                PhoneLookup._ID,
-                PhoneLookup.LOOKUP_KEY,
-                PhoneLookup.DISPLAY_NAME,
-                PhoneLookup.LAST_TIME_CONTACTED,
-                PhoneLookup.TIMES_CONTACTED,
-                PhoneLookup.STARRED,
-                PhoneLookup.IN_DEFAULT_DIRECTORY,
-                PhoneLookup.IN_VISIBLE_GROUP,
-                PhoneLookup.PHOTO_FILE_ID,
-                PhoneLookup.PHOTO_ID,
-                PhoneLookup.PHOTO_URI,
-                PhoneLookup.PHOTO_THUMBNAIL_URI,
-                PhoneLookup.CUSTOM_RINGTONE,
-                PhoneLookup.HAS_PHONE_NUMBER,
-                PhoneLookup.SEND_TO_VOICEMAIL,
-                PhoneLookup.NUMBER,
-                PhoneLookup.TYPE,
-                PhoneLookup.LABEL,
-                PhoneLookup.NORMALIZED_NUMBER
+                Directory._ID,
+                Directory.PACKAGE_NAME,
+                Directory.TYPE_RESOURCE_ID,
+                Directory.DISPLAY_NAME,
+                Directory.ACCOUNT_TYPE,
+                Directory.ACCOUNT_NAME,
         });
 
         // First, convert and make sure it returns an empty cursor.
-        // TODO: Use EnterpriseContactsCursorWrapper instead of rewriteCorpLookup.
-        Cursor rewritten = null;
+        Cursor rewritten = ContactsProvider2.rewriteCorpDirectories(c);
 
         assertEquals(0, rewritten.getCount());
-        assertEquals(19, rewritten.getColumnCount());
+        assertEquals(6, rewritten.getColumnCount());
 
         c.addRow(new Object[] {
-                1L, // PhoneLookup._ID,
-                null, // PhoneLookup.LOOKUP_KEY,
-                null, // PhoneLookup.DISPLAY_NAME,
-                null, // PhoneLookup.LAST_TIME_CONTACTED,
-                null, // PhoneLookup.TIMES_CONTACTED,
-                null, // PhoneLookup.STARRED,
-                null, // PhoneLookup.IN_DEFAULT_DIRECTORY,
-                null, // PhoneLookup.IN_VISIBLE_GROUP,
-                null, // PhoneLookup.PHOTO_FILE_ID,
-                null, // PhoneLookup.PHOTO_ID,
-                null, // PhoneLookup.PHOTO_URI,
-                null, // PhoneLookup.PHOTO_THUMBNAIL_URI,
-                null, // PhoneLookup.CUSTOM_RINGTONE,
-                null, // PhoneLookup.HAS_PHONE_NUMBER,
-                null, // PhoneLookup.SEND_TO_VOICEMAIL,
-                null, // PhoneLookup.NUMBER,
-                null, // PhoneLookup.TYPE,
-                null, // PhoneLookup.LABEL,
-                null, // PhoneLookup.NORMALIZED_NUMBER
+                5L, // Directory._ID
+                "name", // Directory.PACKAGE_NAME
+                123, // Directory.TYPE_RESOURCE_ID
+                "display", // Directory.DISPLAY_NAME
+                "atype", // Directory.ACCOUNT_TYPE
+                "aname", // Directory.ACCOUNT_NAME
         });
 
-        c.addRow(new Object[] {
-                10L, // PhoneLookup._ID,
-                "key", // PhoneLookup.LOOKUP_KEY,
-                "name", // PhoneLookup.DISPLAY_NAME,
-                123, // PhoneLookup.LAST_TIME_CONTACTED,
-                456, // PhoneLookup.TIMES_CONTACTED,
-                1, // PhoneLookup.STARRED,
-                1, // PhoneLookup.IN_DEFAULT_DIRECTORY,
-                1, // PhoneLookup.IN_VISIBLE_GROUP,
-                1001, // PhoneLookup.PHOTO_FILE_ID,
-                1002, // PhoneLookup.PHOTO_ID,
-                "content://a/a", // PhoneLookup.PHOTO_URI,
-                "content://a/b", // PhoneLookup.PHOTO_THUMBNAIL_URI,
-                "content://a/c", // PhoneLookup.CUSTOM_RINGTONE,
-                1, // PhoneLookup.HAS_PHONE_NUMBER,
-                1, // PhoneLookup.SEND_TO_VOICEMAIL,
-                "1234", // PhoneLookup.NUMBER,
-                1, // PhoneLookup.TYPE,
-                "label", // PhoneLookup.LABEL,
-                "+1234", // PhoneLookup.NORMALIZED_NUMBER
-        });
-        // TODO: Use EnterpriseContactsCursorWrapper instead of rewriteCorpLookup
-        // rewritten = ContactsProvider2.rewriteCorpLookup(c.getColumnNames(), c,
-        //         PhoneLookup._ID);
-        assertEquals(2, rewritten.getCount());
-        assertEquals(19, rewritten.getColumnCount());
+        rewritten = ContactsProvider2.rewriteCorpDirectories(c);
+        assertEquals(1, rewritten.getCount());
+        assertEquals(6, rewritten.getColumnCount());
 
         rewritten.moveToPosition(0);
         int column = 0;
-        assertEquals(1000000001L, rewritten.getLong(column++)); // We offset ID for corp contacts.
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-
-
-        rewritten.moveToNext();
-        column = 0;
-        assertEquals(1000000010L, rewritten.getLong(column++)); // With offset.
-        assertEquals("c-key", rewritten.getString(column++));
+        assertEquals(1000000005L, rewritten.getLong(column++));
         assertEquals("name", rewritten.getString(column++));
         assertEquals(123, rewritten.getInt(column++));
-        assertEquals(456, rewritten.getInt(column++));
-        assertEquals(1, rewritten.getInt(column++));
-        assertEquals(1, rewritten.getInt(column++));
-        assertEquals(1, rewritten.getInt(column++));
-        assertEquals(null, rewritten.getString(column++)); // photo file id
-        assertEquals(null, rewritten.getString(column++)); // photo id
-        assertEquals("content://com.android.contacts/contacts_corp/10/display_photo",
-                rewritten.getString(column++));
-        assertEquals("content://com.android.contacts/contacts_corp/10/photo",
-                rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++)); // ringtone
-        assertEquals(1, rewritten.getInt(column++));
-        assertEquals(1, rewritten.getInt(column++));
-        assertEquals("1234", rewritten.getString(column++));
-        assertEquals(1, rewritten.getInt(column++));
-        assertEquals("label", rewritten.getString(column++));
-        assertEquals("+1234", rewritten.getString(column++));
-
-        // Use a narower projection.
-        // TODO: Use EnterpriseContactsCursorWrapper instead of rewriteCorpLookup
-        // rewritten = ContactsProvider2.rewriteCorpLookup(
-        //         new String[] {PhoneLookup.PHOTO_URI, PhoneLookup.PHOTO_THUMBNAIL_URI}, c,
-        //                 PhoneLookup._ID);
-        assertEquals(2, rewritten.getCount());
-        assertEquals(2, rewritten.getColumnCount());
-
-        rewritten.moveToPosition(0);
-        column = 0;
-        assertEquals(null, rewritten.getString(column++));
-        assertEquals(null, rewritten.getString(column++));
-
-
-        rewritten.moveToNext();
-        column = 0;
-        assertEquals("content://com.android.contacts/contacts_corp/10/display_photo",
-                rewritten.getString(column++));
-        assertEquals("content://com.android.contacts/contacts_corp/10/photo",
-                rewritten.getString(column++));
+        assertEquals("display", rewritten.getString(column++));
+        assertEquals("atype", rewritten.getString(column++));
+        assertEquals("aname", rewritten.getString(column++));
     }
 
     public void testPhoneUpdate() {
diff --git a/tests/src/com/android/providers/contacts/EnterpriseContactsCursorWrapperTest.java b/tests/src/com/android/providers/contacts/EnterpriseContactsCursorWrapperTest.java
new file mode 100644
index 0000000..74a74a3
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/EnterpriseContactsCursorWrapperTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.providers.contacts;
+
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.provider.ContactsContract.PhoneLookup;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.providers.contacts.enterprise.EnterpriseContactsCursorWrapper;
+
+
+@SmallTest
+public class EnterpriseContactsCursorWrapperTest extends AndroidTestCase {
+
+    public void testWrappedResults() {
+        // 19 columns
+        final String[] projection = new String[] {
+                /* column 0 */ PhoneLookup._ID,
+                /* column 1 */ PhoneLookup.LOOKUP_KEY,
+                /* column 2 */ PhoneLookup.DISPLAY_NAME,
+                /* column 3 */ PhoneLookup.LAST_TIME_CONTACTED,
+                /* column 4 */ PhoneLookup.TIMES_CONTACTED,
+                /* column 5 */ PhoneLookup.STARRED,
+                /* column 6 */ PhoneLookup.IN_DEFAULT_DIRECTORY,
+                /* column 7 */ PhoneLookup.IN_VISIBLE_GROUP,
+                /* column 8 */ PhoneLookup.PHOTO_FILE_ID,
+                /* column 9 */ PhoneLookup.PHOTO_ID,
+                /* column 10 */ PhoneLookup.PHOTO_URI,
+                /* column 11 */ PhoneLookup.PHOTO_THUMBNAIL_URI,
+                /* column 12 */ PhoneLookup.CUSTOM_RINGTONE,
+                /* column 13 */ PhoneLookup.HAS_PHONE_NUMBER,
+                /* column 14 */ PhoneLookup.SEND_TO_VOICEMAIL,
+                /* column 15 */ PhoneLookup.NUMBER,
+                /* column 16 */ PhoneLookup.TYPE,
+                /* column 17 */ PhoneLookup.LABEL,
+                /* column 18 */ PhoneLookup.NORMALIZED_NUMBER
+        };
+        final MatrixCursor c = new MatrixCursor(projection);
+
+        // First, convert and make sure it returns an empty cursor.
+        Cursor rewritten = new EnterpriseContactsCursorWrapper(c, projection, 0, false, null);
+
+        assertEquals(0, rewritten.getCount());
+        assertEquals(19, rewritten.getColumnCount());
+
+        c.addRow(new Object[] {
+                1L, // PhoneLookup._ID,
+                null, // PhoneLookup.LOOKUP_KEY,
+                null, // PhoneLookup.DISPLAY_NAME,
+                null, // PhoneLookup.LAST_TIME_CONTACTED,
+                null, // PhoneLookup.TIMES_CONTACTED,
+                null, // PhoneLookup.STARRED,
+                null, // PhoneLookup.IN_DEFAULT_DIRECTORY,
+                null, // PhoneLookup.IN_VISIBLE_GROUP,
+                null, // PhoneLookup.PHOTO_FILE_ID,
+                null, // PhoneLookup.PHOTO_ID,
+                null, // PhoneLookup.PHOTO_URI,
+                null, // PhoneLookup.PHOTO_THUMBNAIL_URI,
+                null, // PhoneLookup.CUSTOM_RINGTONE,
+                null, // PhoneLookup.HAS_PHONE_NUMBER,
+                null, // PhoneLookup.SEND_TO_VOICEMAIL,
+                null, // PhoneLookup.NUMBER,
+                null, // PhoneLookup.TYPE,
+                null, // PhoneLookup.LABEL,
+                null, // PhoneLookup.NORMALIZED_NUMBER
+        });
+
+        c.addRow(new Object[] {
+                10L, // PhoneLookup._ID,
+                "key", // PhoneLookup.LOOKUP_KEY,
+                "name", // PhoneLookup.DISPLAY_NAME,
+                123, // PhoneLookup.LAST_TIME_CONTACTED,
+                456, // PhoneLookup.TIMES_CONTACTED,
+                1, // PhoneLookup.STARRED,
+                1, // PhoneLookup.IN_DEFAULT_DIRECTORY,
+                1, // PhoneLookup.IN_VISIBLE_GROUP,
+                1001, // PhoneLookup.PHOTO_FILE_ID,
+                1002, // PhoneLookup.PHOTO_ID,
+                "content://a/a", // PhoneLookup.PHOTO_URI,
+                "content://a/b", // PhoneLookup.PHOTO_THUMBNAIL_URI,
+                "content://a/c", // PhoneLookup.CUSTOM_RINGTONE,
+                1, // PhoneLookup.HAS_PHONE_NUMBER,
+                1, // PhoneLookup.SEND_TO_VOICEMAIL,
+                "1234", // PhoneLookup.NUMBER,
+                1, // PhoneLookup.TYPE,
+                "label", // PhoneLookup.LABEL,
+                "+1234", // PhoneLookup.NORMALIZED_NUMBER
+        });
+        rewritten = new EnterpriseContactsCursorWrapper(c, projection, 0, false, null);
+        assertEquals(2, rewritten.getCount());
+        assertEquals(19, rewritten.getColumnCount());
+
+        rewritten.moveToPosition(0);
+        int column = 0;
+        assertEquals(1000000001L, rewritten.getLong(column++)); // We offset ID for corp contacts.
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++));
+
+
+        rewritten.moveToNext();
+        column = 0;
+        assertEquals(1000000010L, rewritten.getLong(column++)); // With offset.
+        assertEquals("c-key", rewritten.getString(column++));
+        assertEquals("name", rewritten.getString(column++));
+        assertEquals(123, rewritten.getInt(column++));
+        assertEquals(456, rewritten.getInt(column++));
+        assertEquals(1, rewritten.getInt(column++));
+        assertEquals(1, rewritten.getInt(column++));
+        assertEquals(1, rewritten.getInt(column++));
+        assertEquals(null, rewritten.getString(column++)); // photo file id
+        assertEquals(null, rewritten.getString(column++)); // photo id
+        assertEquals("content://com.android.contacts/contacts_corp/10/display_photo",
+                rewritten.getString(column++));
+        assertEquals("content://com.android.contacts/contacts_corp/10/photo",
+                rewritten.getString(column++));
+        assertEquals(null, rewritten.getString(column++)); // ringtone
+        assertEquals(1, rewritten.getInt(column++));
+        assertEquals(1, rewritten.getInt(column++));
+        assertEquals("1234", rewritten.getString(column++));
+        assertEquals(1, rewritten.getInt(column++));
+        assertEquals("label", rewritten.getString(column++));
+        assertEquals("+1234", rewritten.getString(column++));
+
+
+        rewritten = new EnterpriseContactsCursorWrapper(c, projection, 0, false, null);
+
+        assertEquals(2, rewritten.getCount());
+        assertEquals(19, rewritten.getColumnCount());
+
+        rewritten.moveToPosition(0);
+
+        assertEquals(null, rewritten.getString(10));
+        assertEquals(null, rewritten.getString(11));
+
+
+        rewritten.moveToNext();
+        column = 0;
+        assertEquals("content://com.android.contacts/contacts_corp/10/display_photo",
+                rewritten.getString(10));
+        assertEquals("content://com.android.contacts/contacts_corp/10/photo",
+                rewritten.getString(11));
+    }
+}
+