Merge "No more use audio mime type for voicemail uris."
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index e57ceab..8e92eca 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -4354,7 +4354,11 @@
 
         // If the cursor doesn't contain a snippet column, don't bother wrapping it.
         if (cursor.getColumnIndex(SearchSnippetColumns.SNIPPET) < 0) {
-            return cursor;
+            if (VERBOSE_LOGGING) {
+                return new InstrumentedCursorWrapper(cursor, uri, TAG);
+            } else {
+                return cursor;
+            }
         }
 
         // Parse out snippet arguments for use when snippets are retrieved from the cursor.
@@ -4375,8 +4379,13 @@
         int maxTokens = args != null && args.length > 3 ? Integer.parseInt(args[3])
                 : DEFAULT_SNIPPET_ARG_MAX_TOKENS;
 
-        return new SnippetizingCursorWrapper(cursor, query, startMatch, endMatch, ellipsis,
-                maxTokens);
+        if (VERBOSE_LOGGING) {
+            return new InstrumentedCursorWrapper(new SnippetizingCursorWrapper(
+                    cursor, query, startMatch, endMatch, ellipsis, maxTokens), uri, TAG);
+        } else {
+            return new SnippetizingCursorWrapper(cursor, query, startMatch, endMatch, ellipsis,
+                    maxTokens);
+        }
     }
 
     private CrossProcessCursor getCrossProcessCursor(Cursor cursor) {
diff --git a/src/com/android/providers/contacts/CrossProcessCursorWrapper.java b/src/com/android/providers/contacts/CrossProcessCursorWrapper.java
new file mode 100644
index 0000000..76baa96
--- /dev/null
+++ b/src/com/android/providers/contacts/CrossProcessCursorWrapper.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 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.CrossProcessCursor;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.database.CursorWrapper;
+
+/**
+ * Cursor wrapper that implements {@link CrossProcessCursor}, but will only behave as such if the
+ * cursor it is wrapping is itself a {@link CrossProcessCursor} or another wrapper around the same.
+ */
+public class CrossProcessCursorWrapper extends CursorWrapper implements CrossProcessCursor {
+
+    // The cross process cursor.  Only non-null if the wrapped cursor was a cross-process cursor.
+    private final CrossProcessCursor mCrossProcessCursor;
+
+    public CrossProcessCursorWrapper(Cursor cursor) {
+        super(cursor);
+        mCrossProcessCursor = getCrossProcessCursor(cursor);
+    }
+
+    private CrossProcessCursor getCrossProcessCursor(Cursor cursor) {
+        if (cursor instanceof CrossProcessCursor) {
+            return (CrossProcessCursor) cursor;
+        } else if (cursor instanceof CursorWrapper) {
+            return getCrossProcessCursor(((CursorWrapper) cursor).getWrappedCursor());
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void fillWindow(int pos, CursorWindow window) {
+        if (mCrossProcessCursor != null) {
+            mCrossProcessCursor.fillWindow(pos, window);
+        } else {
+            throw new UnsupportedOperationException("Wrapped cursor is not a cross-process cursor");
+        }
+    }
+
+    @Override
+    public CursorWindow getWindow() {
+        if (mCrossProcessCursor != null) {
+            return mCrossProcessCursor.getWindow();
+        } else {
+            throw new UnsupportedOperationException("Wrapped cursor is not a cross-process cursor");
+        }
+    }
+
+    @Override
+    public boolean onMove(int oldPosition, int newPosition) {
+        if (mCrossProcessCursor != null) {
+            return mCrossProcessCursor.onMove(oldPosition, newPosition);
+        } else {
+            throw new UnsupportedOperationException("Wrapped cursor is not a cross-process cursor");
+        }
+    }
+
+}
diff --git a/src/com/android/providers/contacts/InstrumentedCursorWrapper.java b/src/com/android/providers/contacts/InstrumentedCursorWrapper.java
new file mode 100644
index 0000000..c412810
--- /dev/null
+++ b/src/com/android/providers/contacts/InstrumentedCursorWrapper.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2011 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 com.google.android.collect.Lists;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Cursor wrapper that handles tracking time taken before query result came back and how long
+ * the cursor was open before it was closed.
+ */
+public class InstrumentedCursorWrapper extends CrossProcessCursorWrapper {
+
+    /**
+     * Static list of active cursors.
+     */
+    private static List<InstrumentedCursorWrapper> mActiveCursors = Lists.newArrayList();
+
+    /**
+     * Time (ms since epoch) when the cursor was created.
+     */
+    private long mCreationTime;
+
+    /**
+     * Milliseconds after creation at which the query completed (triggered by a getCount or
+     * any method that moves the cursor).
+     */
+    private long mTimeToQuery;
+
+    /**
+     * The URI being queried in this cursor.
+     */
+    private Uri mUri;
+
+    /**
+     * Log tag to use.
+     */
+    private String mTag;
+
+    public InstrumentedCursorWrapper(Cursor cursor, Uri uri, String tag) {
+        super(cursor);
+        mCreationTime = System.currentTimeMillis();
+        mUri = uri;
+        mTag = tag;
+        mActiveCursors.add(this);
+    }
+
+    @Override
+    public int getCount() {
+        int count = super.getCount();
+        logQueryTime();
+        return count;
+    }
+
+    @Override
+    public boolean moveToFirst() {
+        boolean result = super.moveToFirst();
+        logQueryTime();
+        return result;
+    }
+
+    @Override
+    public boolean moveToLast() {
+        boolean result = super.moveToLast();
+        logQueryTime();
+        return result;
+    }
+
+    @Override
+    public boolean move(int offset) {
+        boolean result = super.move(offset);
+        logQueryTime();
+        return result;
+    }
+
+    @Override
+    public boolean moveToPosition(int position) {
+        boolean result = super.moveToPosition(position);
+        logQueryTime();
+        return result;
+    }
+
+    @Override
+    public boolean moveToNext() {
+        boolean result = super.moveToNext();
+        logQueryTime();
+        return result;
+    }
+
+    @Override
+    public boolean moveToPrevious() {
+        boolean result = super.moveToPrevious();
+        logQueryTime();
+        return result;
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        long timeToClose = System.currentTimeMillis() - mCreationTime;
+        Log.v(mTag, timeToClose + "ms to close for URI " + mUri
+                + " (" + (timeToClose - mTimeToQuery) + "ms since query complete)");
+        mActiveCursors.remove(this);
+        Log.v(mTag, mActiveCursors.size() + " cursors still open");
+    }
+
+    private void logQueryTime() {
+        if (mTimeToQuery == 0) {
+            mTimeToQuery = System.currentTimeMillis() - mCreationTime;
+            Log.v(mTag, mTimeToQuery + "ms to query URI " + mUri);
+        }
+    }
+}
diff --git a/src/com/android/providers/contacts/SnippetizingCursorWrapper.java b/src/com/android/providers/contacts/SnippetizingCursorWrapper.java
index 73fd2a3..61c5dcd 100644
--- a/src/com/android/providers/contacts/SnippetizingCursorWrapper.java
+++ b/src/com/android/providers/contacts/SnippetizingCursorWrapper.java
@@ -18,8 +18,6 @@
 
 import android.database.CrossProcessCursor;
 import android.database.Cursor;
-import android.database.CursorWindow;
-import android.database.CursorWrapper;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.SearchSnippetColumns;
 import android.text.TextUtils;
@@ -38,15 +36,12 @@
  * Note that this wrapper implements {@link CrossProcessCursor}, but will only behave as such if the
  * cursor it is wrapping is itself a {@link CrossProcessCursor} or another wrapper around the same.
  */
-public class SnippetizingCursorWrapper extends CursorWrapper implements CrossProcessCursor {
+public class SnippetizingCursorWrapper extends CrossProcessCursorWrapper {
 
     // Pattern for splitting a line into tokens.  This matches e-mail addresses as a single token,
     // otherwise splitting on any group of non-alphanumeric characters.
     Pattern SPLIT_PATTERN = Pattern.compile("([\\w-\\.]+)@((?:[\\w]+\\.)+)([a-zA-Z]{2,4})|[\\w]+");
 
-    // The cross process cursor.  Only non-null if the wrapped cursor was a cross-process cursor.
-    private final CrossProcessCursor mCrossProcessCursor;
-
     // Index of the snippet field (if any).
     private final int mSnippetIndex;
 
@@ -71,10 +66,9 @@
      * @param ellipsis Ellipsis characters to use at the start or end of the snippet if appropriate.
      * @param maxTokens Maximum number of tokens to include in the snippet.
      */
-    SnippetizingCursorWrapper(Cursor cursor, String query, String startMatch,
-            String endMatch, String ellipsis, int maxTokens) {
+    SnippetizingCursorWrapper(Cursor cursor, String query, String startMatch, String endMatch,
+            String ellipsis, int maxTokens) {
         super(cursor);
-        mCrossProcessCursor = getCrossProcessCursor(cursor);
         mSnippetIndex = getColumnIndex(SearchSnippetColumns.SNIPPET);
         mQuery = query;
         mStartMatch = startMatch;
@@ -84,43 +78,6 @@
         mDoSnippetizing = mQuery.split(ContactsProvider2.QUERY_TOKENIZER_REGEX).length == 1;
     }
 
-    private CrossProcessCursor getCrossProcessCursor(Cursor cursor) {
-        if (cursor instanceof CrossProcessCursor) {
-            return (CrossProcessCursor) cursor;
-        } else if (cursor instanceof CursorWrapper) {
-            return getCrossProcessCursor(((CursorWrapper) cursor).getWrappedCursor());
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public void fillWindow(int pos, CursorWindow window) {
-        if (mCrossProcessCursor != null) {
-            mCrossProcessCursor.fillWindow(pos, window);
-        } else {
-            throw new UnsupportedOperationException("Wrapped cursor is not a cross-process cursor");
-        }
-    }
-
-    @Override
-    public CursorWindow getWindow() {
-        if (mCrossProcessCursor != null) {
-            return mCrossProcessCursor.getWindow();
-        } else {
-            throw new UnsupportedOperationException("Wrapped cursor is not a cross-process cursor");
-        }
-    }
-
-    @Override
-    public boolean onMove(int oldPosition, int newPosition) {
-        if (mCrossProcessCursor != null) {
-            return mCrossProcessCursor.onMove(oldPosition, newPosition);
-        } else {
-            throw new UnsupportedOperationException("Wrapped cursor is not a cross-process cursor");
-        }
-    }
-
     @Override
     public String getString(int columnIndex) {
         String columnContent = super.getString(columnIndex);