Merge "Enterprise phone lookup should return consistent result..."
diff --git a/src/com/android/providers/contacts/CallLogBackupAgent.java b/src/com/android/providers/contacts/CallLogBackupAgent.java
index e5c77e6..8e160c8 100644
--- a/src/com/android/providers/contacts/CallLogBackupAgent.java
+++ b/src/com/android/providers/contacts/CallLogBackupAgent.java
@@ -26,6 +26,7 @@
 import android.provider.CallLog;
 import android.provider.CallLog.Calls;
 import android.telecom.PhoneAccountHandle;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -41,6 +42,8 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.SortedSet;
 import java.util.TreeSet;
 
@@ -55,7 +58,8 @@
         SortedSet<Integer> callIds;
     }
 
-    private static class Call {
+    @VisibleForTesting
+    static class Call {
         int id;
         long date;
         long duration;
@@ -82,7 +86,8 @@
     private static final String TAG = "CallLogBackupAgent";
 
     /** Current version of CallLogBackup. Used to track the backup format. */
-    private static final int VERSION = 1;
+    @VisibleForTesting
+    static final int VERSION = 1002;
     /** Version indicating that there exists no previous backup entry. */
     @VisibleForTesting
     static final int VERSION_NO_PREVIOUS_STATE = 0;
@@ -119,7 +124,7 @@
         }
 
         // Run the actual backup of data
-        runBackup(state, data);
+        runBackup(state, data, getAllCallLogEntries());
 
         // Rewrite the backup state.
         DataOutputStream dataOutput = new DataOutputStream(new BufferedOutputStream(
@@ -151,59 +156,62 @@
     }
 
     @VisibleForTesting
-    void runBackup(CallLogBackupState state, BackupDataOutput data) {
+    void runBackup(CallLogBackupState state, BackupDataOutput data, Iterable<Call> calls) {
         SortedSet<Integer> callsToRemove = new TreeSet<>(state.callIds);
 
-        // Get all the existing call log entries.
-        Cursor cursor = getAllCallLogEntries();
-        if (cursor == null) {
-            return;
+        // Loop through all the call log entries to identify:
+        // (1) new calls
+        // (2) calls which have been deleted.
+        for (Call call : calls) {
+            if (!state.callIds.contains(call.id)) {
+
+                if (isDebug()) {
+                    Log.d(TAG, "Adding call to backup: " + call);
+                }
+
+                // This call new (not in our list from the last backup), lets back it up.
+                addCallToBackup(data, call);
+                state.callIds.add(call.id);
+            } else {
+                // This call still exists in the current call log so delete it from the
+                // "callsToRemove" set since we want to keep it.
+                callsToRemove.remove(call.id);
+            }
         }
 
-        try {
-            // Loop through all the call log entries to identify:
-            // (1) new calls
-            // (2) calls which have been deleted.
-            while (cursor.moveToNext()) {
-                Call call = readCallFromCursor(cursor);
-
-                if (!state.callIds.contains(call.id)) {
-
-                    if (isDebug()) {
-                        Log.d(TAG, "Adding call to backup: " + call);
-                    }
-
-                    // This call new (not in our list from the last backup), lets back it up.
-                    addCallToBackup(data, call);
-                    state.callIds.add(call.id);
-                } else {
-                    // This call still exists in the current call log so delete it from the
-                    // "callsToRemove" set since we want to keep it.
-                    callsToRemove.remove(call.id);
-                }
+        // Remove calls which no longer exist in the set.
+        for (Integer i : callsToRemove) {
+            if (isDebug()) {
+                Log.d(TAG, "Removing call from backup: " + i);
             }
 
-            // Remove calls which no longer exist in the set.
-            for (Integer i : callsToRemove) {
-                if (isDebug()) {
-                    Log.d(TAG, "Removing call from backup: " + i);
-                }
-
-                removeCallFromBackup(data, i);
-                state.callIds.remove(i);
-            }
-
-        } finally {
-            cursor.close();
+            removeCallFromBackup(data, i);
+            state.callIds.remove(i);
         }
     }
 
-    private Cursor getAllCallLogEntries() {
+    private Iterable<Call> getAllCallLogEntries() {
+        List<Call> calls = new LinkedList<>();
+
         // We use the API here instead of querying ContactsDatabaseHelper directly because
         // CallLogProvider has special locks in place for sychronizing when to read.  Using the APIs
         // gives us that for free.
         ContentResolver resolver = getContentResolver();
-        return resolver.query(CallLog.Calls.CONTENT_URI, CALL_LOG_PROJECTION, null, null, null);
+        Cursor cursor = resolver.query(CallLog.Calls.CONTENT_URI, CALL_LOG_PROJECTION, null, null, null);
+        if (cursor != null) {
+            try {
+                while (cursor.moveToNext()) {
+                    Call call = readCallFromCursor(cursor);
+                    if (call != null) {
+                        calls.add(call);
+                    }
+                }
+            } finally {
+                cursor.close();
+            }
+        }
+
+        return calls;
     }
 
     private void writeCallToProvider(Call call) {
@@ -277,10 +285,10 @@
             if (version >= 1) {
                 call.date = dataInput.readLong();
                 call.duration = dataInput.readLong();
-                call.number = dataInput.readUTF();
+                call.number = readString(dataInput, version);
                 call.type = dataInput.readInt();
                 call.numberPresentation = dataInput.readInt();
-                call.accountComponentName = dataInput.readUTF();
+                call.accountComponentName = readString(dataInput, version);
                 call.accountId = dataInput.readUTF();
                 call.accountAddress = dataInput.readUTF();
                 call.dataUsage = dataInput.readLong();
@@ -322,13 +330,13 @@
             data.writeInt(VERSION);
             data.writeLong(call.date);
             data.writeLong(call.duration);
-            data.writeUTF(call.number);
+            writeString(data, call.number);
             data.writeInt(call.type);
             data.writeInt(call.numberPresentation);
-            data.writeUTF(call.accountComponentName);
-            data.writeUTF(call.accountId);
-            data.writeUTF(call.accountAddress);
-            data.writeLong(call.dataUsage);
+            writeString(data, call.accountComponentName);
+            writeString(data, call.accountId);
+            writeString(data, call.accountAddress);
+            data.writeLong(call.dataUsage == null ? 0 : call.dataUsage);
             data.writeInt(call.features);
             data.flush();
 
@@ -343,6 +351,27 @@
         }
     }
 
+    private void writeString(DataOutputStream data, String str) throws IOException {
+        if (str == null) {
+            data.writeBoolean(false);
+        } else {
+            data.writeBoolean(true);
+            data.writeUTF(str);
+        }
+    }
+
+    private String readString(DataInputStream data, int version) throws IOException {
+        if (version == 1) {
+            return data.readUTF();
+        } else {
+            if (data.readBoolean()) {
+                return data.readUTF();
+            } else {
+                return null;
+            }
+        }
+    }
+
     private void removeCallFromBackup(BackupDataOutput output, int callId) {
         try {
             output.writeEntityHeader(Integer.toString(callId), -1);
diff --git a/tests/src/com/android/providers/contacts/CallLogBackupAgentTest.java b/tests/src/com/android/providers/contacts/CallLogBackupAgentTest.java
index 2699f65..ca9af91 100644
--- a/tests/src/com/android/providers/contacts/CallLogBackupAgentTest.java
+++ b/tests/src/com/android/providers/contacts/CallLogBackupAgentTest.java
@@ -16,17 +16,20 @@
 
 package com.android.providers.contacts;
 
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.eq;
 
+import android.app.backup.BackupDataOutput;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.providers.contacts.CallLogBackupAgent.Call;
 import com.android.providers.contacts.CallLogBackupAgent.CallLogBackupState;
 
 import org.mockito.InOrder;
+import org.mockito.Matchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -34,6 +37,8 @@
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.EOFException;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.TreeSet;
 
 /**
@@ -44,6 +49,7 @@
 
     @Mock DataInput mDataInput;
     @Mock DataOutput mDataOutput;
+    @Mock BackupDataOutput mBackupDataOutput;
 
     CallLogBackupAgent mCallLogBackupAgent;
 
@@ -107,32 +113,33 @@
 
     public void testWriteState_NoCalls() throws Exception {
         CallLogBackupState state = new CallLogBackupState();
-        state.version = 1;
+        state.version = CallLogBackupAgent.VERSION;
         state.callIds = new TreeSet<>();
 
         mCallLogBackupAgent.writeState(mDataOutput, state);
 
         InOrder inOrder = Mockito.inOrder(mDataOutput);
-        inOrder.verify(mDataOutput).writeInt(1 /* version */);
+        inOrder.verify(mDataOutput).writeInt(CallLogBackupAgent.VERSION);
         inOrder.verify(mDataOutput).writeInt(0 /* size */);
     }
 
     public void testWriteState_OneCall() throws Exception {
         CallLogBackupState state = new CallLogBackupState();
-        state.version = 1;
+        state.version = CallLogBackupAgent.VERSION;
         state.callIds = new TreeSet<>();
         state.callIds.add(101);
 
         mCallLogBackupAgent.writeState(mDataOutput, state);
 
         InOrder inOrder = Mockito.inOrder(mDataOutput);
-        inOrder.verify(mDataOutput, times(2)).writeInt(1);
+        inOrder.verify(mDataOutput).writeInt(CallLogBackupAgent.VERSION);
+        inOrder.verify(mDataOutput).writeInt(1);
         inOrder.verify(mDataOutput).writeInt(101 /* call-ID */);
     }
 
     public void testWriteState_MultipleCalls() throws Exception {
         CallLogBackupState state = new CallLogBackupState();
-        state.version = 1;
+        state.version = CallLogBackupAgent.VERSION;
         state.callIds = new TreeSet<>();
         state.callIds.add(101);
         state.callIds.add(102);
@@ -141,10 +148,83 @@
         mCallLogBackupAgent.writeState(mDataOutput, state);
 
         InOrder inOrder = Mockito.inOrder(mDataOutput);
-        inOrder.verify(mDataOutput).writeInt(1 /* version */);
+        inOrder.verify(mDataOutput).writeInt(CallLogBackupAgent.VERSION);
         inOrder.verify(mDataOutput).writeInt(3 /* size */);
         inOrder.verify(mDataOutput).writeInt(101 /* call-ID */);
         inOrder.verify(mDataOutput).writeInt(102 /* call-ID */);
         inOrder.verify(mDataOutput).writeInt(103 /* call-ID */);
     }
+
+    public void testRunBackup_NoCalls() throws Exception {
+        CallLogBackupState state = new CallLogBackupState();
+        state.version = CallLogBackupAgent.VERSION;
+        state.callIds = new TreeSet<>();
+        List<Call> calls = new LinkedList<>();
+
+        mCallLogBackupAgent.runBackup(state, mBackupDataOutput, calls);
+
+        Mockito.verifyNoMoreInteractions(mBackupDataOutput);
+    }
+
+    public void testRunBackup_OneNewCall() throws Exception {
+        CallLogBackupState state = new CallLogBackupState();
+        state.version = CallLogBackupAgent.VERSION;
+        state.callIds = new TreeSet<>();
+        List<Call> calls = new LinkedList<>();
+        calls.add(makeCall(101, 0L, 0L, "555-5555"));
+        mCallLogBackupAgent.runBackup(state, mBackupDataOutput, calls);
+
+        verify(mBackupDataOutput).writeEntityHeader(eq("101"), Matchers.anyInt());
+        verify(mBackupDataOutput).writeEntityData((byte[]) Matchers.any(), Matchers.anyInt());
+    }
+
+    public void testRunBackup_MultipleCall() throws Exception {
+        CallLogBackupState state = new CallLogBackupState();
+        state.version = CallLogBackupAgent.VERSION;
+        state.callIds = new TreeSet<>();
+        List<Call> calls = new LinkedList<>();
+        calls.add(makeCall(101, 0L, 0L, "555-1234"));
+        calls.add(makeCall(102, 0L, 0L, "555-5555"));
+
+        mCallLogBackupAgent.runBackup(state, mBackupDataOutput, calls);
+
+        InOrder inOrder = Mockito.inOrder(mBackupDataOutput);
+        inOrder.verify(mBackupDataOutput).writeEntityHeader(eq("101"), Matchers.anyInt());
+        inOrder.verify(mBackupDataOutput).
+                writeEntityData((byte[]) Matchers.any(), Matchers.anyInt());
+        inOrder.verify(mBackupDataOutput).writeEntityHeader(eq("102"), Matchers.anyInt());
+        inOrder.verify(mBackupDataOutput).
+                writeEntityData((byte[]) Matchers.any(), Matchers.anyInt());
+    }
+
+    public void testRunBackup_PartialMultipleCall() throws Exception {
+        CallLogBackupState state = new CallLogBackupState();
+
+        state.version = CallLogBackupAgent.VERSION;
+        state.callIds = new TreeSet<>();
+        state.callIds.add(101);
+
+        List<Call> calls = new LinkedList<>();
+        calls.add(makeCall(101, 0L, 0L, "555-1234"));
+        calls.add(makeCall(102, 0L, 0L, "555-5555"));
+
+        mCallLogBackupAgent.runBackup(state, mBackupDataOutput, calls);
+
+        InOrder inOrder = Mockito.inOrder(mBackupDataOutput);
+        inOrder.verify(mBackupDataOutput).writeEntityHeader(eq("102"), Matchers.anyInt());
+        inOrder.verify(mBackupDataOutput).
+                writeEntityData((byte[]) Matchers.any(), Matchers.anyInt());
+    }
+
+    private static Call makeCall(int id, long date, long duration, String number) {
+        Call c = new Call();
+        c.id = id;
+        c.date = date;
+        c.duration = duration;
+        c.number = number;
+        c.accountComponentName = "account-component";
+        c.accountId = "account-id";
+        return c;
+    }
+
 }