Merge "When view's expand, scroll them onto screen" into lmp-dev
diff --git a/res/drawable/bottom_border_background.xml b/res/drawable/bottom_border_background.xml
deleted file mode 100644
index 7dad5a4..0000000
--- a/res/drawable/bottom_border_background.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
-
-<item>
-    <shape android:shape="rectangle" >
-        <solid android:color="@color/favorite_contacts_separator_color" />
-        <padding android:bottom="1dp" />
-    </shape>
-</item>
-<item>
-    <shape android:shape="rectangle" >
-        <solid android:color="@color/background_dialer_list_items" />
-    </shape>
-</item>
-</layer-list>
\ No newline at end of file
diff --git a/res/drawable/bottom_border_background_pressed.xml b/res/drawable/bottom_border_background_pressed.xml
deleted file mode 100644
index fc5a5b8..0000000
--- a/res/drawable/bottom_border_background_pressed.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
-    <item android:drawable="@drawable/bottom_border_background" />
-    <item android:drawable="@drawable/list_selector_background_transition_holo_light" />
-</layer-list>
\ No newline at end of file
diff --git a/res/drawable/contact_list_item_background.xml b/res/drawable/contact_list_item_background.xml
deleted file mode 100644
index 5637f4d..0000000
--- a/res/drawable/contact_list_item_background.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_pressed="true" android:drawable="@drawable/bottom_border_background_pressed"  />
-    <item android:drawable="@drawable/bottom_border_background" />
-</selector>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 1fe8aec..6fc9cf4 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -103,6 +103,7 @@
     <!-- Hide the actionbar title during the activity preview -->
     <style name="DialtactsActivityTheme" parent="DialtactsTheme">
         <item name="android:actionBarStyle">@style/DialtactsActionBarWithoutTitleStyle</item>
+        <item name="android:fastScrollThumbDrawable">@drawable/fastscroll_thumb</item>
     </style>
 
     <style name="CallDetailActivityTheme" parent="DialtactsThemeWithoutActionBarOverlay">
diff --git a/src/com/android/dialer/SpecialCharSequenceMgr.java b/src/com/android/dialer/SpecialCharSequenceMgr.java
index 0b6e881..37c0383 100644
--- a/src/com/android/dialer/SpecialCharSequenceMgr.java
+++ b/src/com/android/dialer/SpecialCharSequenceMgr.java
@@ -36,6 +36,7 @@
 import android.widget.EditText;
 import android.widget.Toast;
 
+import com.android.common.io.MoreCloseables;
 import com.android.contacts.common.database.NoNullCursorAsyncQueryHandler;
 
 /**
@@ -377,34 +378,38 @@
          */
         @Override
         protected void onNotNullableQueryComplete(int token, Object cookie, Cursor c) {
-            sPreviousAdnQueryHandler = null;
-            if (mCanceled) {
-                return;
-            }
+            try {
+                sPreviousAdnQueryHandler = null;
+                if (mCanceled) {
+                    return;
+                }
 
-            SimContactQueryCookie sc = (SimContactQueryCookie) cookie;
+                SimContactQueryCookie sc = (SimContactQueryCookie) cookie;
 
-            // close the progress dialog.
-            sc.progressDialog.dismiss();
+                // close the progress dialog.
+                sc.progressDialog.dismiss();
 
-            // get the EditText to update or see if the request was cancelled.
-            EditText text = sc.getTextField();
+                // get the EditText to update or see if the request was cancelled.
+                EditText text = sc.getTextField();
 
-            // if the textview is valid, and the cursor is valid and postionable
-            // on the Nth number, then we update the text field and display a
-            // toast indicating the caller name.
-            if ((c != null) && (text != null) && (c.moveToPosition(sc.contactNum))) {
-                String name = c.getString(c.getColumnIndexOrThrow(ADN_NAME_COLUMN_NAME));
-                String number = c.getString(c.getColumnIndexOrThrow(ADN_PHONE_NUMBER_COLUMN_NAME));
+                // if the textview is valid, and the cursor is valid and postionable
+                // on the Nth number, then we update the text field and display a
+                // toast indicating the caller name.
+                if ((c != null) && (text != null) && (c.moveToPosition(sc.contactNum))) {
+                    String name = c.getString(c.getColumnIndexOrThrow(ADN_NAME_COLUMN_NAME));
+                    String number = c.getString(c.getColumnIndexOrThrow(ADN_PHONE_NUMBER_COLUMN_NAME));
 
-                // fill the text in.
-                text.getText().replace(0, 0, number);
+                    // fill the text in.
+                    text.getText().replace(0, 0, number);
 
-                // display the name as a toast
-                Context context = sc.progressDialog.getContext();
-                name = context.getString(R.string.menu_callNumber, name);
-                Toast.makeText(context, name, Toast.LENGTH_SHORT)
-                    .show();
+                    // display the name as a toast
+                    Context context = sc.progressDialog.getContext();
+                    name = context.getString(R.string.menu_callNumber, name);
+                    Toast.makeText(context, name, Toast.LENGTH_SHORT)
+                        .show();
+                }
+            } finally {
+                MoreCloseables.closeQuietly(c);
             }
         }
 
diff --git a/src/com/android/dialer/calllog/CallLogActivity.java b/src/com/android/dialer/calllog/CallLogActivity.java
index 8e5622c..036241c 100644
--- a/src/com/android/dialer/calllog/CallLogActivity.java
+++ b/src/com/android/dialer/calllog/CallLogActivity.java
@@ -234,7 +234,8 @@
     }
 
     @Override
-    public void onCallsFetched(Cursor statusCursor) {
-        // Do nothing. Implemented to satisfy CallLogQueryHandler.Listener.
+    public boolean onCallsFetched(Cursor statusCursor) {
+        // Return false; did not take ownership of cursor
+        return false;
     }
 }
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index 921a1c4..a90fc55 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -1299,10 +1299,13 @@
                         Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, number),
                         PhoneQuery._PROJECTION, null, null, null);
                 if (phonesCursor != null) {
-                    if (phonesCursor.moveToFirst()) {
-                        matchingNumber = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
+                    try {
+                        if (phonesCursor.moveToFirst()) {
+                            matchingNumber = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
+                        }
+                    } finally {
+                        phonesCursor.close();
                     }
-                    phonesCursor.close();
                 }
             } catch (Exception e) {
                 // Use the number from the call log
diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java
index 4822e84..2f0ee53 100644
--- a/src/com/android/dialer/calllog/CallLogFragment.java
+++ b/src/com/android/dialer/calllog/CallLogFragment.java
@@ -43,7 +43,6 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
-import com.android.common.io.MoreCloseables;
 import com.android.contacts.common.GeoUtil;
 import com.android.contacts.common.util.ViewUtil;
 import com.android.dialer.R;
@@ -228,9 +227,10 @@
 
     /** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */
     @Override
-    public void onCallsFetched(Cursor cursor) {
+    public boolean onCallsFetched(Cursor cursor) {
         if (getActivity() == null || getActivity().isFinishing()) {
-            return;
+            // Return false; we did not take ownership of the cursor
+            return false;
         }
         mAdapter.setLoading(false);
         mAdapter.changeCursor(cursor);
@@ -263,6 +263,7 @@
         }
         mCallLogFetched = true;
         destroyEmptyLoaderIfAllDataFetched();
+        return true;
     }
 
     /**
@@ -277,7 +278,6 @@
 
         int activeSources = mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor);
         setVoicemailSourcesAvailable(activeSources != 0);
-        MoreCloseables.closeQuietly(statusCursor);
         mVoicemailStatusFetched = true;
         destroyEmptyLoaderIfAllDataFetched();
     }
diff --git a/src/com/android/dialer/calllog/CallLogQueryHandler.java b/src/com/android/dialer/calllog/CallLogQueryHandler.java
index 64eddec..dfc9c78 100644
--- a/src/com/android/dialer/calllog/CallLogQueryHandler.java
+++ b/src/com/android/dialer/calllog/CallLogQueryHandler.java
@@ -42,8 +42,6 @@
 import java.lang.ref.WeakReference;
 import java.util.List;
 
-import javax.annotation.concurrent.GuardedBy;
-
 /** Handles asynchronous queries to the call log. */
 public class CallLogQueryHandler extends NoNullCursorAsyncQueryHandler {
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
@@ -72,26 +70,6 @@
 
     private final WeakReference<Listener> mListener;
 
-    /** The cursor containing the old calls, or null if they have not yet been fetched. */
-    @GuardedBy("this") private Cursor mCallLogCursor;
-    /**
-     * The identifier of the latest calls request.
-     * <p>
-     * A request for the list of calls requires two queries and hence the two cursor
-     * and {@link #mCallLogCursor} above, corresponding to {@link #QUERY_CALLLOG_TOKEN}.
-     * <p>
-     * When a new request is about to be started, existing cursors are closed. However, it is
-     * possible that one of the queries completes after the new request has started. This means that
-     * we might merge two cursors that do not correspond to the same request. Moreover, this may
-     * lead to a resource leak if the same query completes and we override the cursor without
-     * closing it first.
-     * <p>
-     * To make sure we only join two cursors from the same request, we use this variable to store
-     * the request id of the latest request and make sure we only process cursors corresponding to
-     * the this request.
-     */
-    @GuardedBy("this") private int mCallsRequestId;
-
     /**
      * Simple handler that wraps background calls to catch
      * {@link SQLiteException}, such as when the disk is full.
@@ -142,8 +120,7 @@
      */
     public void fetchCalls(int callType, long newerThan) {
         cancelFetch();
-        int requestId = newCallsRequest();
-        fetchCalls(QUERY_CALLLOG_TOKEN, requestId, callType, false /* newOnly */, newerThan);
+        fetchCalls(QUERY_CALLLOG_TOKEN, callType, false /* newOnly */, newerThan);
     }
 
     public void fetchCalls(int callType) {
@@ -156,8 +133,7 @@
     }
 
     /** Fetches the list of calls in the call log. */
-    private void fetchCalls(int token, int requestId, int callType, boolean newOnly,
-            long newerThan) {
+    private void fetchCalls(int token, int callType, boolean newOnly, long newerThan) {
         // We need to check for NULL explicitly otherwise entries with where READ is NULL
         // may not match either the query or its negation.
         // We consider the calls that are not yet consumed (i.e. IS_READ = 0) as "new".
@@ -192,7 +168,7 @@
         Uri uri = Calls.CONTENT_URI_WITH_VOICEMAIL.buildUpon()
                 .appendQueryParameter(Calls.LIMIT_PARAM_KEY, Integer.toString(limit))
                 .build();
-        startQuery(token, requestId, uri,
+        startQuery(token, null, uri,
                 CallLogQuery._PROJECTION, selection, selectionArgs.toArray(EMPTY_STRING_ARRAY),
                 Calls.DEFAULT_SORT_ORDER);
     }
@@ -247,52 +223,39 @@
                 where.toString(), null);
     }
 
-    /**
-     * Start a new request and return its id. The request id will be used as the cookie for the
-     * background request.
-     * <p>
-     * Closes any open cursor that has not yet been sent to the requester.
-     */
-    private synchronized int newCallsRequest() {
-        MoreCloseables.closeQuietly(mCallLogCursor);
-        mCallLogCursor = null;
-        return ++mCallsRequestId;
-    }
-
     @Override
     protected synchronized void onNotNullableQueryComplete(int token, Object cookie, Cursor cursor) {
-        if (token == QUERY_CALLLOG_TOKEN) {
-            int requestId = ((Integer) cookie).intValue();
-            if (requestId != mCallsRequestId) {
-                // Ignore this query since it does not correspond to the latest request.
-                return;
-            }
-
-            // Store the returned cursor.
-            MoreCloseables.closeQuietly(mCallLogCursor);
-            mCallLogCursor = cursor;
-        } else if (token == QUERY_VOICEMAIL_STATUS_TOKEN) {
-            updateVoicemailStatus(cursor);
-            return;
-        } else {
-            Log.w(TAG, "Unknown query completed: ignoring: " + token);
+        if (cursor == null) {
             return;
         }
-
-        if (mCallLogCursor != null) {
-            updateAdapterData(mCallLogCursor);
-            mCallLogCursor = null;
+        try {
+            if (token == QUERY_CALLLOG_TOKEN) {
+                if (updateAdapterData(cursor)) {
+                    cursor = null;
+                }
+            } else if (token == QUERY_VOICEMAIL_STATUS_TOKEN) {
+                updateVoicemailStatus(cursor);
+            } else {
+                Log.w(TAG, "Unknown query completed: ignoring: " + token);
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
         }
     }
 
     /**
      * Updates the adapter in the call log fragment to show the new cursor data.
+     * Returns true if the listener took ownership of the cursor.
      */
-    private void updateAdapterData(Cursor combinedCursor) {
+    private boolean updateAdapterData(Cursor cursor) {
         final Listener listener = mListener.get();
         if (listener != null) {
-            listener.onCallsFetched(combinedCursor);
+            return listener.onCallsFetched(cursor);
         }
+        return false;
+
     }
 
     private void updateVoicemailStatus(Cursor statusCursor) {
@@ -309,7 +272,8 @@
 
         /**
          * Called when {@link CallLogQueryHandler#fetchCalls(int)}complete.
+         * Returns true if takes ownership of cursor.
          */
-        void onCallsFetched(Cursor combinedCursor);
+        boolean onCallsFetched(Cursor combinedCursor);
     }
 }
diff --git a/src/com/android/dialer/database/DialerDatabaseHelper.java b/src/com/android/dialer/database/DialerDatabaseHelper.java
index 95249a6..511c2a7 100644
--- a/src/com/android/dialer/database/DialerDatabaseHelper.java
+++ b/src/com/android/dialer/database/DialerDatabaseHelper.java
@@ -441,17 +441,19 @@
 
     public String getProperty(SQLiteDatabase db, String key, String defaultValue) {
         try {
+            String value = null;
             final Cursor cursor = db.query(Tables.PROPERTIES,
                     new String[] {PropertiesColumns.PROPERTY_VALUE},
                             PropertiesColumns.PROPERTY_KEY + "=?",
                     new String[] {key}, null, null, null);
-            String value = null;
-            try {
-                if (cursor.moveToFirst()) {
-                    value = cursor.getString(0);
+            if (cursor != null) {
+                try {
+                    if (cursor.moveToFirst()) {
+                        value = cursor.getString(0);
+                    }
+                } finally {
+                    cursor.close();
                 }
-            } finally {
-                cursor.close();
             }
             return value != null ? value : defaultValue;
         } catch (SQLiteException e) {
@@ -770,14 +772,6 @@
             final Cursor updatedContactCursor = mContext.getContentResolver().query(PhoneQuery.URI,
                     PhoneQuery.PROJECTION, PhoneQuery.SELECTION,
                     new String[]{lastUpdateMillis}, null);
-
-            /** Sets the time after querying the database as the current update time. */
-            final Long currentMillis = System.currentTimeMillis();
-
-            if (DEBUG) {
-                stopWatch.lap("Queried the Contacts database");
-            }
-
             if (updatedContactCursor == null) {
                 if (DEBUG) {
                     Log.e(TAG, "SmartDial query received null for cursor");
@@ -785,18 +779,25 @@
                 return;
             }
 
-            /** Prevents the app from reading the dialer database when updating. */
-            sInUpdate.getAndSet(true);
-
-            /** Removes contacts that have been deleted. */
-            removeDeletedContacts(db, lastUpdateMillis);
-            removePotentiallyCorruptedContacts(db, lastUpdateMillis);
-
-            if (DEBUG) {
-                stopWatch.lap("Finished deleting deleted entries");
-            }
+            /** Sets the time after querying the database as the current update time. */
+            final Long currentMillis = System.currentTimeMillis();
 
             try {
+                if (DEBUG) {
+                    stopWatch.lap("Queried the Contacts database");
+                }
+
+                /** Prevents the app from reading the dialer database when updating. */
+                sInUpdate.getAndSet(true);
+
+                /** Removes contacts that have been deleted. */
+                removeDeletedContacts(db, lastUpdateMillis);
+                removePotentiallyCorruptedContacts(db, lastUpdateMillis);
+
+                if (DEBUG) {
+                    stopWatch.lap("Finished deleting deleted entries");
+                }
+
                 /** If the database did not exist before, jump through deletion as there is nothing
                  * to delete.
                  */
@@ -830,12 +831,12 @@
                     " WHERE " + SmartDialDbColumns.LAST_SMARTDIAL_UPDATE_TIME +
                     " = " + Long.toString(currentMillis),
                     new String[] {});
-            if (DEBUG) {
-                stopWatch.lap("Queried the smart dial table for contact names");
-            }
-
             if (nameCursor != null) {
                 try {
+                    if (DEBUG) {
+                        stopWatch.lap("Queried the smart dial table for contact names");
+                    }
+
                     /** Inserts prefixes of names into the prefix table.*/
                     insertNamePrefixes(db, nameCursor);
                     if (DEBUG) {
@@ -936,26 +937,28 @@
                     " LIKE '" + looseQuery + "')" +
                 " ORDER BY " + SmartDialSortingOrder.SORT_ORDER,
                 new String[] {currentTimeStamp});
-
-        if (DEBUG) {
-            stopWatch.lap("Prefix query completed");
+        if (cursor == null) {
+            return result;
         }
-
-        /** Gets the column ID from the cursor.*/
-        final int columnDataId = 0;
-        final int columnDisplayNamePrimary = 1;
-        final int columnPhotoId = 2;
-        final int columnNumber = 3;
-        final int columnId = 4;
-        final int columnLookupKey = 5;
-        if (DEBUG) {
-            stopWatch.lap("Found column IDs");
-        }
-
-        final Set<ContactMatch> duplicates = new HashSet<ContactMatch>();
-        int counter = 0;
         try {
             if (DEBUG) {
+                stopWatch.lap("Prefix query completed");
+            }
+
+            /** Gets the column ID from the cursor.*/
+            final int columnDataId = 0;
+            final int columnDisplayNamePrimary = 1;
+            final int columnPhotoId = 2;
+            final int columnNumber = 3;
+            final int columnId = 4;
+            final int columnLookupKey = 5;
+            if (DEBUG) {
+                stopWatch.lap("Found column IDs");
+            }
+
+            final Set<ContactMatch> duplicates = new HashSet<ContactMatch>();
+            int counter = 0;
+            if (DEBUG) {
                 stopWatch.lap("Moved cursor to start");
             }
             /** Iterates the cursor to find top contact suggestions without duplication.*/
diff --git a/src/com/android/dialer/interactions/PhoneNumberInteraction.java b/src/com/android/dialer/interactions/PhoneNumberInteraction.java
index dc35b06..b61a496 100644
--- a/src/com/android/dialer/interactions/PhoneNumberInteraction.java
+++ b/src/com/android/dialer/interactions/PhoneNumberInteraction.java
@@ -380,13 +380,17 @@
 
     @Override
     public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) {
-        if (cursor == null || !isSafeToCommitTransactions()) {
+        if (cursor == null) {
             onDismiss();
             return;
         }
-        ArrayList<PhoneItem> phoneList = new ArrayList<PhoneItem>();
-        String primaryPhone = null;
         try {
+            ArrayList<PhoneItem> phoneList = new ArrayList<PhoneItem>();
+            String primaryPhone = null;
+            if (!isSafeToCommitTransactions()) {
+                onDismiss();
+                return;
+            }
             while (cursor.moveToNext()) {
                 if (mContactId == UNKNOWN_CONTACT_ID) {
                     mContactId = cursor.getLong(CONTACT_ID);
@@ -408,28 +412,27 @@
 
                 phoneList.add(item);
             }
+
+            if (mUseDefault && primaryPhone != null) {
+                performAction(primaryPhone);
+                onDismiss();
+                return;
+            }
+
+            Collapser.collapseList(phoneList, mContext);
+            if (phoneList.size() == 0) {
+                onDismiss();
+            } else if (phoneList.size() == 1) {
+                PhoneItem item = phoneList.get(0);
+                onDismiss();
+                performAction(item.phoneNumber);
+            } else {
+                // There are multiple candidates. Let the user choose one.
+                showDisambiguationDialog(phoneList);
+            }
         } finally {
             cursor.close();
         }
-
-        if (mUseDefault && primaryPhone != null) {
-            performAction(primaryPhone);
-            onDismiss();
-            return;
-        }
-
-        Collapser.collapseList(phoneList, mContext);
-
-        if (phoneList.size() == 0) {
-            onDismiss();
-        } else if (phoneList.size() == 1) {
-            PhoneItem item = phoneList.get(0);
-            onDismiss();
-            performAction(item.phoneNumber);
-        } else {
-            // There are multiple candidates. Let the user choose one.
-            showDisambiguationDialog(phoneList);
-        }
     }
 
     private boolean isSafeToCommitTransactions() {
diff --git a/src/com/android/dialer/list/ListsFragment.java b/src/com/android/dialer/list/ListsFragment.java
index ed95dc2..39efa51 100644
--- a/src/com/android/dialer/list/ListsFragment.java
+++ b/src/com/android/dialer/list/ListsFragment.java
@@ -111,12 +111,11 @@
     private PanelSlideListener mPanelSlideListener = new PanelSlideListener() {
         @Override
         public void onPanelSlide(View panel, float slideOffset) {
-            // For every 1 percent that the panel is slid upwards, clip 1.5 percent from each edge
-            // of the shortcut card, to achieve the animated effect of the shortcut card
-            // rapidly shrinking and disappearing from view when the panel is slid upwards.
-            // slideOffset is 1 when the shortcut card is fully exposed, and 0 when completely
-            // hidden.
-            float ratioCardHidden = (1 - slideOffset) * 1.5f;
+            // For every 1 percent that the panel is slid upwards, clip 1 percent off the top
+            // edge of the shortcut card, to achieve the animated effect of the shortcut card
+            // being pushed out of view when the panel is slid upwards. slideOffset is 1 when
+            // the shortcut card is fully exposed, and 0 when completely hidden.
+            float ratioCardHidden = (1 - slideOffset);
             if (mShortcutCardsListView.getChildCount() > 0) {
                 final SwipeableShortcutCard v =
                         (SwipeableShortcutCard) mShortcutCardsListView.getChildAt(0);
@@ -263,7 +262,7 @@
     }
 
     @Override
-    public void onCallsFetched(Cursor cursor) {
+    public boolean onCallsFetched(Cursor cursor) {
         mCallLogAdapter.setLoading(false);
 
         // Save the date of the most recent call log item
@@ -273,6 +272,8 @@
 
         mCallLogAdapter.changeCursor(cursor);
         mMergedAdapter.notifyDataSetChanged();
+        // Return true; took ownership of cursor
+        return true;
     }
 
     @Override
diff --git a/src/com/android/dialer/list/ShortcutCardsAdapter.java b/src/com/android/dialer/list/ShortcutCardsAdapter.java
index 4bd914f..09f4e49 100644
--- a/src/com/android/dialer/list/ShortcutCardsAdapter.java
+++ b/src/com/android/dialer/list/ShortcutCardsAdapter.java
@@ -52,6 +52,10 @@
     }
 
     private static final String TAG = ShortcutCardsAdapter.class.getSimpleName();
+    private static final float CLIP_CARD_BARELY_HIDDEN_RATIO = 0.001f;
+    private static final float CLIP_CARD_MOSTLY_HIDDEN_RATIO = 0.9f;
+    // Fade out 5x faster than the hidden ratio.
+    private static final float CLIP_CARD_OPACITY_RATIO = 5f;
 
     private final CallLogAdapter mCallLogAdapter;
 
@@ -98,10 +102,12 @@
         public void onVoicemailStatusFetched(Cursor statusCursor) {}
 
         @Override
-        public void onCallsFetched(Cursor combinedCursor) {
+        public boolean onCallsFetched(Cursor combinedCursor) {
             mCallLogAdapter.invalidateCache();
             mCallLogAdapter.changeCursor(combinedCursor);
             mCallLogAdapter.notifyDataSetChanged();
+            // Return true; took ownership of cursor
+            return true;
         }
     };
 
@@ -339,34 +345,31 @@
             int width = viewToClip.getWidth();
             int height = viewToClip.getHeight();
 
-            if (ratioHidden <= 0.001f) {
+            if (ratioHidden <= CLIP_CARD_BARELY_HIDDEN_RATIO) {
                 viewToClip.setTranslationZ(mPreviousTranslationZ);
             } else if (viewToClip.getTranslationZ() != 0){
                 mPreviousTranslationZ = viewToClip.getTranslationZ();
                 viewToClip.setTranslationZ(0);
             }
 
-            if (ratioHidden > 0.5f) {
+            if (ratioHidden > CLIP_CARD_MOSTLY_HIDDEN_RATIO) {
                 mClipRect.set(0, 0 , 0, 0);
                 setVisibility(View.INVISIBLE);
             } else {
                 setVisibility(View.VISIBLE);
-                int newLeft = (int) (ratioHidden * mCardMaxHorizontalClip);
-                int newRight = width - newLeft;
                 int newTop = (int) (ratioHidden * height);
-                int newBottom = (height - newTop);
-                mClipRect.set(newLeft, newTop, newRight, newBottom);
+                mClipRect.set(0, newTop, width, height);
 
                 // Since the pane will be overlapping with the action bar, apply a vertical offset
-                // to visually center the clipped card in the viewable area;
-                int verticalOffset = -newTop / 2;
-                viewToClip.setTranslationY(verticalOffset);
+                // to top align the clipped card in the viewable area;
+                viewToClip.setTranslationY(-newTop);
             }
             viewToClip.setClipBounds(mClipRect);
 
             // If the view has any children, fade them out of view.
             final ViewGroup viewGroup = (ViewGroup) viewToClip;
-            setChildrenOpacity(viewGroup, Math.max(0, 1 - 4.5f * ratioHidden));
+            setChildrenOpacity(
+                    viewGroup, Math.max(0, 1 - (CLIP_CARD_OPACITY_RATIO  * ratioHidden)));
         }
 
         private void setChildrenOpacity(ViewGroup viewGroup, float alpha) {