Merge "Add BIND_CALL_SERVICE permission to Dialer" into klp-dev
diff --git a/res/drawable/bottom_border_background_pressed.xml b/res/drawable/bottom_border_background_pressed.xml
new file mode 100644
index 0000000..0924914
--- /dev/null
+++ b/res/drawable/bottom_border_background_pressed.xml
@@ -0,0 +1,19 @@
+<?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="@*android: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
new file mode 100644
index 0000000..5637f4d
--- /dev/null
+++ b/res/drawable/contact_list_item_background.xml
@@ -0,0 +1,19 @@
+<?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/layout/phone_favorites_fragment.xml b/res/layout/phone_favorites_fragment.xml
index 6023fc8..4d3abf4 100644
--- a/res/layout/phone_favorites_fragment.xml
+++ b/res/layout/phone_favorites_fragment.xml
@@ -45,5 +45,11 @@
             android:layout_marginTop="@dimen/empty_message_top_margin"
             android:textColor="?android:attr/textColorSecondary"
             android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+        <ImageView
+            android:id="@+id/contact_tile_drag_shadow_overlay"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:visibility="gone"/>
     </FrameLayout>
 </LinearLayout>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index f0b4d82..7d7071e 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -32,7 +32,7 @@
     <string name="recentCalls_empty" msgid="247053222448663107">"Gesprekkenoverzicht is leeg"</string>
     <string name="clearCallLogConfirmation_title" msgid="6427524640461816332">"Oproeplog wissen?"</string>
     <string name="clearCallLogConfirmation" msgid="5043563133171583152">"Al uw oproepgegevens worden verwijderd."</string>
-    <string name="clearCallLogProgress_title" msgid="8365943000154295771">"Oproeplogboek wissen..."</string>
+    <string name="clearCallLogProgress_title" msgid="8365943000154295771">"Gesprekkenlijst wissen..."</string>
   <plurals name="notification_voicemail_title">
     <item quantity="one" msgid="1746619685488504230">"Voicemail"</item>
     <item quantity="other" msgid="5513481419205061254">"<xliff:g id="COUNT">%1$d</xliff:g> voicemails"</item>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 2717049..90ed648 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -24,12 +24,12 @@
     <string name="menu_sendTextMessage" msgid="6937343460284499306">"发送短信"</string>
     <string name="recentCalls_callNumber" msgid="1756372533999226126">"呼叫<xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="recentCalls_editNumberBeforeCall" msgid="7756171675833267857">"呼叫之前编辑号码"</string>
-    <string name="recentCalls_addToContact" msgid="1429899535546487008">"添加到“联系人”"</string>
+    <string name="recentCalls_addToContact" msgid="1429899535546487008">"添加到通讯录"</string>
     <string name="recentCalls_removeFromRecentList" msgid="401662244636511330">"从通话记录中删除"</string>
     <string name="recentCalls_deleteAll" msgid="6352364392762163704">"清除通话记录"</string>
     <string name="recentCalls_trashVoicemail" msgid="7604696960787435655">"删除语音邮件"</string>
     <string name="recentCalls_shareVoicemail" msgid="1416112847592942840">"分享语音邮件"</string>
-    <string name="recentCalls_empty" msgid="247053222448663107">"通话记录为空。"</string>
+    <string name="recentCalls_empty" msgid="247053222448663107">"没有通话记录。"</string>
     <string name="clearCallLogConfirmation_title" msgid="6427524640461816332">"要清除通话记录吗?"</string>
     <string name="clearCallLogConfirmation" msgid="5043563133171583152">"系统将删除您的所有通话记录。"</string>
     <string name="clearCallLogProgress_title" msgid="8365943000154295771">"正在清除通话记录..."</string>
@@ -89,7 +89,7 @@
     <string name="menu_show_missed_only" msgid="154473166059743996">"仅显示未接来电"</string>
     <string name="menu_show_voicemails_only" msgid="1898421289561435703">"只显示语音邮件"</string>
     <string name="menu_show_all_calls" msgid="7560347482073345885">"显示所有通话"</string>
-    <string name="add_2sec_pause" msgid="9214012315201040129">"暂停时间延长 2 秒"</string>
+    <string name="add_2sec_pause" msgid="9214012315201040129">"暂停时间延长2秒"</string>
     <string name="add_wait" msgid="3360818652790319634">"延长等待时间"</string>
     <string name="call_settings" msgid="7666474782093693667">"设置"</string>
     <string name="menu_newContact" msgid="1209922412763274638">"新建联系人"</string>
diff --git a/res/values/animation_constants.xml b/res/values/animation_constants.xml
index 77b7627..b41b316 100644
--- a/res/values/animation_constants.xml
+++ b/res/values/animation_constants.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License
   -->
 <resources>
-    <integer name="fade_duration">250</integer>
+    <integer name="fade_duration">300</integer>
 
     <!-- Swipe constants -->
     <integer name="swipe_escape_velocity">100</integer>
@@ -27,4 +27,4 @@
     <dimen name="min_swipe">5dip</dimen>
     <dimen name="min_vert">10dip</dimen>
     <dimen name="min_lock">20dip</dimen>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index 4680038..4c747c9 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -639,12 +639,15 @@
             return;
         }
 
-        if (mDialpadFragment != null && (phoneIsInUse() || isDialIntent(intent))) {
-            mDialpadFragment.setStartedFromNewIntent(true);
-            if (!mDialpadFragment.isVisible()) {
-                mInCallDialpadUp = true;
+        if (mDialpadFragment != null) {
+            final boolean phoneIsInUse = phoneIsInUse();
+            if (phoneIsInUse || isDialIntent(intent)) {
+                mDialpadFragment.setStartedFromNewIntent(true);
+                if (phoneIsInUse && !mDialpadFragment.isVisible()) {
+                    mInCallDialpadUp = true;
+                }
+                showDialpadFragment(false);
             }
-            showDialpadFragment(false);
         }
     }
 
diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java
index ce27ab7..c8e2613 100644
--- a/src/com/android/dialer/calllog/CallLogFragment.java
+++ b/src/com/android/dialer/calllog/CallLogFragment.java
@@ -432,8 +432,7 @@
         updateOnTransition(true);
     }
 
-    // TODO krelease: Figure out if we still need this. If so, it should be probably be moved to
-    // the call log activity instead, or done only in a single call log fragment.
+    // TODO: Move to CallLogActivity
     private void updateOnTransition(boolean onEntry) {
         // We don't want to update any call data when keyguard is on because the user has likely not
         // seen the new calls yet.
@@ -441,6 +440,7 @@
         if (mKeyguardManager != null && !mKeyguardManager.inKeyguardRestrictedInputMode()) {
             // On either of the transitions we update the missed call and voicemail notifications.
             // While exiting we additionally consume all missed calls (by marking them as read).
+            mCallLogQueryHandler.markNewCallsAsOld();
             if (!onEntry) {
                 mCallLogQueryHandler.markMissedCallsAsRead();
             }
diff --git a/src/com/android/dialer/calllog/CallLogQueryHandler.java b/src/com/android/dialer/calllog/CallLogQueryHandler.java
index 987dedf..def3c97 100644
--- a/src/com/android/dialer/calllog/CallLogQueryHandler.java
+++ b/src/com/android/dialer/calllog/CallLogQueryHandler.java
@@ -138,22 +138,14 @@
      * <p>
      * It will asynchronously update the content of the list view when the fetch completes.
      */
-    public void fetchCalls(int callType) {
+    public void fetchCalls(int callType, long newerThan) {
         cancelFetch();
         int requestId = newCallsRequest();
-        fetchCalls(QUERY_CALLLOG_TOKEN, requestId, callType , false /* newOnly */);
+        fetchCalls(QUERY_CALLLOG_TOKEN, requestId, callType, false /* newOnly */, newerThan);
     }
 
-    /**
-     * Fetches the list of calls from the call log for a given type.
-     * This call fetches only the new (i.e. NEW = 1) ones.
-     * <p>
-     * It will asynchronously update the content of the list view when the fetch completes.
-     */
-    public void fetchNewCalls(int callType) {
-        cancelFetch();
-        int requestId = newCallsRequest();
-        fetchCalls(QUERY_CALLLOG_TOKEN, requestId, callType , true /* newOnly */);
+    public void fetchCalls(int callType) {
+        fetchCalls(callType, 0);
     }
 
     public void fetchVoicemailStatus() {
@@ -162,7 +154,8 @@
     }
 
     /** Fetches the list of calls in the call log. */
-    private void fetchCalls(int token, int requestId, int callType, boolean newOnly) {
+    private void fetchCalls(int token, int requestId, 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".
@@ -180,8 +173,18 @@
             }
             // Add a clause to fetch only items of type voicemail.
             where.append(String.format("(%s = ?)", Calls.TYPE));
+            // Add a clause to fetch only items newer than the requested date
             selectionArgs.add(Integer.toString(callType));
         }
+
+        if (newerThan > 0) {
+            if (where.length() > 0) {
+                where.append(" AND ");
+            }
+            where.append(String.format("(%s > ?)", Calls.DATE));
+            selectionArgs.add(Long.toString(newerThan));
+        }
+
         final int limit = (mLogLimit == -1) ? NUM_LOGS_TO_DISPLAY : mLogLimit;
         final String selection = where.length() > 0 ? where.toString() : null;
         Uri uri = Calls.CONTENT_URI_WITH_VOICEMAIL.buildUpon()
diff --git a/src/com/android/dialer/list/PhoneFavoriteFragment.java b/src/com/android/dialer/list/PhoneFavoriteFragment.java
index d8179e7..beeb320 100644
--- a/src/com/android/dialer/list/PhoneFavoriteFragment.java
+++ b/src/com/android/dialer/list/PhoneFavoriteFragment.java
@@ -18,9 +18,12 @@
 import android.app.Activity;
 import android.app.Fragment;
 import android.app.LoaderManager;
+import android.content.Context;
 import android.content.CursorLoader;
 import android.content.Loader;
+import android.content.SharedPreferences;
 import android.database.Cursor;
+import android.database.MatrixCursor;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Bundle;
@@ -33,6 +36,7 @@
 import android.widget.AbsListView;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.TextView;
 
@@ -41,7 +45,9 @@
 import com.android.contacts.common.GeoUtil;
 import com.android.contacts.common.list.ContactEntry;
 import com.android.contacts.common.list.ContactTileView;
+import com.android.dialer.DialtactsActivity;
 import com.android.dialer.R;
+import com.android.dialer.calllog.CallLogQuery;
 import com.android.dialer.calllog.ContactInfoHelper;
 import com.android.dialer.calllog.CallLogAdapter;
 import com.android.dialer.calllog.CallLogQueryHandler;
@@ -66,13 +72,16 @@
     private static final String TAG = PhoneFavoriteFragment.class.getSimpleName();
     private static final boolean DEBUG = true;
 
-    private static final int ANIMATION_DURATION = 300;
+    private int mAnimationDuration;
 
     /**
      * Used with LoaderManager.
      */
     private static int LOADER_ID_CONTACT_TILE = 1;
 
+    private static final String KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE =
+            "key_last_dismissed_call_shortcut_date";
+
     public interface OnShowAllContactsListener {
         public void onShowAllContacts();
     }
@@ -156,6 +165,17 @@
      */
     private View mEmptyView;
 
+    /**
+     * Call shortcuts older than this date (persisted in shared preferences) will not show up in
+     * at the top of the screen
+     */
+    private long mLastCallShortcutDate = 0;
+
+    /**
+     * The date of the current call shortcut that is showing on screen.
+     */
+    private long mCurrentCallShortcutDate = 0;
+
     private final ContactTileView.Listener mContactTileAdapterListener =
             new ContactTileAdapterListener();
     private final LoaderManager.LoaderCallbacks<Cursor> mContactTileLoaderListener =
@@ -182,6 +202,7 @@
         if (DEBUG) Log.d(TAG, "onCreate()");
         super.onCreate(savedState);
 
+        mAnimationDuration = getResources().getInteger(R.integer.fade_duration);
         mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(),
                 this, 1);
         final String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
@@ -193,6 +214,11 @@
     @Override
     public void onResume() {
         super.onResume();
+        final SharedPreferences prefs = getActivity().getSharedPreferences(
+                DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+
+        mLastCallShortcutDate = prefs.getLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, 0);
+
         fetchCalls();
         mCallLogAdapter.setLoading(true);
         getLoaderManager().getLoader(LOADER_ID_CONTACT_TILE).forceLoad();
@@ -213,6 +239,10 @@
         mListView.setOnItemSwipeListener(mContactTileAdapter);
         mListView.setOnDragDropListener(mContactTileAdapter);
 
+        final ImageView dragShadowOverlay =
+                (ImageView) listLayout.findViewById(R.id.contact_tile_drag_shadow_overlay);
+        mListView.setDragShadowOverlay(dragShadowOverlay);
+
         mEmptyView = inflater.inflate(R.layout.phone_no_favorites, mListView, false);
 
         mShowAllContactsButton = inflater.inflate(R.layout.show_all_contact_button, mListView,
@@ -225,7 +255,7 @@
         });
 
         mContactTileAdapter.setEmptyView(mEmptyView);
-        mAdapter = new PhoneFavoriteMergedAdapter(getActivity(), mContactTileAdapter,
+        mAdapter = new PhoneFavoriteMergedAdapter(getActivity(), this, mContactTileAdapter,
                 mCallLogAdapter, mShowAllContactsButton);
 
         mListView.setAdapter(mAdapter);
@@ -302,13 +332,20 @@
     @Override
     public void onCallsFetched(Cursor cursor) {
         mCallLogAdapter.setLoading(false);
+
+        // Save the date of the most recent call log item
+        if (cursor != null && cursor.moveToFirst()) {
+            mCurrentCallShortcutDate = cursor.getLong(CallLogQuery.DATE);
+        }
+
         mCallLogAdapter.changeCursor(cursor);
+
         mAdapter.notifyDataSetChanged();
     }
 
     @Override
     public void fetchCalls() {
-        mCallLogQueryHandler.fetchNewCalls(CallLogQueryHandler.CALL_TYPE_ALL);
+        mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL, mLastCallShortcutDate);
     }
 
     @Override
@@ -372,25 +409,13 @@
             final ContactEntry entry = list.get(i);
             final long itemId = mContactTileAdapter.getAdjustedItemId(entry.id);
 
-            // Skip animation for this view if the caller specified that it should be
-            // kept in place
-            if (containsId(idsInPlace, itemId)) continue;
-
-            Integer startLeft = mItemIdLeftMap.get(itemId);
-            int left = child.getLeft();
-            if (DEBUG) {
-                Log.d(TAG, "Found itemId: " + itemId + " for tileview child " + i +
-                        " Left: " + left);
+            if (containsId(idsInPlace, itemId)) {
+                child.setAlpha(0.0f);
+                child.animate().alpha(1.0f)
+                        .setDuration(mAnimationDuration)
+                        .start();
+                break;
             }
-            if (startLeft != null) {
-                if (startLeft != left) {
-                    int delta = startLeft - left;
-                    child.setTranslationX(delta);
-                    child.animate().setDuration(ANIMATION_DURATION).translationX(0);
-                }
-            }
-            // No need to worry about horizontal offsets of new views that come into view since
-            // there is no horizontal scrolling involved.
         }
     }
 
@@ -424,33 +449,12 @@
 
                     final long itemId = mAdapter.getItemId(position);
 
-                    // Skip animation for this view if the caller specified that it should be
-                    // kept in place
-                    if (containsId(idsInPlace, itemId)) continue;
-
-                    Integer startTop = mItemIdTopMap.get(itemId);
-                    final int top = child.getTop();
-                    if (DEBUG) {
-                        Log.d(TAG, "Found itemId: " + itemId + " for listview child " + i +
-                                " Top: " + top);
-                    }
-                    int delta = 0;
-                    if (startTop != null) {
-                        if (startTop != top) {
-                            delta = startTop - top;
-                        }
-                    } else if (!mItemIdLeftMap.containsKey(itemId)) {
-                        // Animate new views along with the others. The catch is that they did not
-                        // exist in the start state, so we must calculate their starting position
-                        // based on neighboring views.
-                        int childHeight = child.getHeight() + mListView.getDividerHeight();
-                        startTop = top + (i > 0 ? childHeight : -childHeight);
-                        delta = startTop - top;
-                    }
-
-                    if (delta != 0) {
-                        child.setTranslationY(delta);
-                        child.animate().setDuration(ANIMATION_DURATION).translationY(0);
+                    if (containsId(idsInPlace, itemId)) {
+                        child.setAlpha(0.0f);
+                        child.animate().alpha(1.0f)
+                                .setDuration(mAnimationDuration)
+                                .start();
+                        break;
                     }
                 }
                 mItemIdTopMap.clear();
@@ -479,4 +483,13 @@
     public void cacheOffsetsForDatasetChange() {
         saveOffsets();
     }
+
+    public void dismissShortcut() {
+        mLastCallShortcutDate = mCurrentCallShortcutDate;
+        final SharedPreferences prefs = getActivity().getSharedPreferences(
+                DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+        prefs.edit().putLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, mLastCallShortcutDate)
+                .apply();
+        fetchCalls();
+    }
 }
diff --git a/src/com/android/dialer/list/PhoneFavoriteListView.java b/src/com/android/dialer/list/PhoneFavoriteListView.java
index 4ce5df1..04bbe6b 100644
--- a/src/com/android/dialer/list/PhoneFavoriteListView.java
+++ b/src/com/android/dialer/list/PhoneFavoriteListView.java
@@ -17,11 +17,11 @@
 
 package com.android.dialer.list;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
 import android.os.Handler;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -29,6 +29,8 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
 import android.widget.ListView;
 
 import com.android.dialer.R;
@@ -66,7 +68,10 @@
     private boolean mIsDragScrollerRunning = false;
     private int mTouchDownForDragStartX;
     private int mTouchDownForDragStartY;
+
     private Bitmap mDragShadowBitmap;
+    private ImageView mDragShadowOverlay;
+    private int mAnimationDuration;
 
     // X and Y offsets inside the item from where the user grabbed to the
     // child's left coordinate. This is used to aid in the drawing of the drag shadow.
@@ -78,8 +83,7 @@
     private int mDragShadowWidth;
     private int mDragShadowHeight;
 
-    private final int DRAG_SHADOW_ALPHA = 180;
-    private final Paint mPaint = new Paint();
+    private final float DRAG_SHADOW_ALPHA = 0.7f;
 
     /**
      * {@link #mTopScrollBound} and {@link mBottomScrollBound} will be
@@ -99,6 +103,28 @@
         }
     };
 
+    private final AnimatorListenerAdapter mDragShadowOverAnimatorListener =
+            new AnimatorListenerAdapter() {
+        private void recycleDragShadow() {
+            if (mDragShadowBitmap != null) {
+                mDragShadowBitmap.recycle();
+                mDragShadowBitmap = null;
+            }
+            mDragShadowOverlay.setVisibility(GONE);
+            mDragShadowOverlay.setImageBitmap(null);
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            recycleDragShadow();
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            recycleDragShadow();
+        }
+    };
+
     public PhoneFavoriteListView(Context context) {
         this(context, null);
     }
@@ -109,12 +135,12 @@
 
     public PhoneFavoriteListView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        mAnimationDuration = context.getResources().getInteger(R.integer.fade_duration);
         mDensityScale = getResources().getDisplayMetrics().density;
         mTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
         mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this,
                 mDensityScale, mTouchSlop);
         setItemsCanFocus(true);
-        mPaint.setAlpha(DRAG_SHADOW_ALPHA);
     }
 
     @Override
@@ -251,7 +277,9 @@
                 ensureScrollHandler();
                 mScrollHandler.removeCallbacks(mDragScroller);
                 mIsDragScrollerRunning = false;
-                if (action != DragEvent.ACTION_DRAG_EXITED) {
+                // Either it's been a successful drop or it's ended with out drop.
+                if (action == DragEvent.ACTION_DROP ||
+                        (action == DragEvent.ACTION_DRAG_ENDED && !event.getResult())) {
                     handleDragFinished(eX, eY);
                 }
                 break;
@@ -262,13 +290,8 @@
         return true;
     }
 
-    @Override
-    public void dispatchDraw(Canvas canvas) {
-        super.dispatchDraw(canvas);
-        // Draw the drag shadow at its last known location if the drag shadow exists.
-        if (mDragShadowBitmap != null) {
-            canvas.drawBitmap(mDragShadowBitmap, mDragShadowLeft, mDragShadowTop, mPaint);
-        }
+    public void setDragShadowOverlay(ImageView overlay) {
+        mDragShadowOverlay = overlay;
     }
 
     /**
@@ -292,6 +315,14 @@
         }
     }
 
+    private FrameLayout.LayoutParams getDragShadowLayoutParams() {
+        final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+                mDragShadowWidth, mDragShadowHeight);
+        lp.leftMargin = mDragShadowLeft;
+        lp.topMargin = mDragShadowTop;
+        return lp;
+    }
+
     /**
      * @return True if the drag is started.
      */
@@ -307,6 +338,11 @@
         if (itemIndex != -1 && mOnDragDropListener != null) {
             final PhoneFavoriteTileView tileView =
                     (PhoneFavoriteTileView) tile.getViewAtPosition(x, y);
+            if (mDragShadowOverlay == null) {
+                return false;
+            }
+
+            mDragShadowOverlay.clearAnimation();
             mDragShadowBitmap = createDraggedChildBitmap(tileView);
             if (mDragShadowBitmap == null) {
                 return false;
@@ -321,9 +357,16 @@
                 mDragShadowLeft = tileView.getLeft() + tileView.getParentRow().getLeft();
                 mDragShadowTop = tileView.getTop() + tileView.getParentRow().getTop();
             }
+
             mDragShadowWidth = tileView.getWidth();
             mDragShadowHeight = tileView.getHeight();
 
+            mDragShadowOverlay.setImageBitmap(mDragShadowBitmap);
+            mDragShadowOverlay.setVisibility(VISIBLE);
+            mDragShadowOverlay.setAlpha(DRAG_SHADOW_ALPHA);
+
+            mDragShadowOverlay.setLayoutParams(getDragShadowLayoutParams());
+
             // x and y passed in are the coordinates of where the user has touched down, calculate
             // the offset to the top left coordinate of the dragged child.  This will be used for
             // drawing the drag shadow.
@@ -350,8 +393,10 @@
         mDragShadowLeft = x - mTouchOffsetToChildLeft;
         mDragShadowTop = y - mTouchOffsetToChildTop;
 
-        // invalidate to trigger a redraw of the drag shadow.
-        invalidate();
+        // Draw the drag shadow at its last known location if the drag shadow exists.
+        if (mDragShadowOverlay != null) {
+            mDragShadowOverlay.setLayoutParams(getDragShadowLayoutParams());
+        }
 
         final ContactTileRow tile = (ContactTileRow) child;
         final int itemIndex = tile.getItemIndex(x, y);
@@ -365,9 +410,12 @@
         mDragShadowLeft = x - mTouchOffsetToChildLeft;
         mDragShadowTop = y - mTouchOffsetToChildTop;
 
-        if (mDragShadowBitmap != null) {
-            mDragShadowBitmap.recycle();
-            mDragShadowBitmap = null;
+        if (mDragShadowOverlay != null) {
+            mDragShadowOverlay.clearAnimation();
+            mDragShadowOverlay.animate().alpha(0.0f)
+                    .setDuration(mAnimationDuration)
+                    .setListener(mDragShadowOverAnimatorListener)
+                    .start();
         }
 
         if (mOnDragDropListener != null) {
diff --git a/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java b/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java
index c7554e2..cf2aeee 100644
--- a/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java
+++ b/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java
@@ -53,6 +53,7 @@
     private final PhoneFavoritesTileAdapter mContactTileAdapter;
     private final CallLogAdapter mCallLogAdapter;
     private final View mShowAllContactsButton;
+    private final PhoneFavoriteFragment mFragment;
 
     private final int mCallLogPadding;
 
@@ -70,7 +71,7 @@
             mCallLogQueryHandler.markNewVoicemailsAsOld();
             CallLogNotificationsHelper.removeMissedCallNotifications();
             CallLogNotificationsHelper.updateVoicemailNotifications(mContext);
-            mCallLogQueryHandler.fetchNewCalls(CallLogQueryHandler.CALL_TYPE_ALL);
+            mFragment.dismissShortcut();
         }
 
         @Override
@@ -96,11 +97,13 @@
     };
 
     public PhoneFavoriteMergedAdapter(Context context,
+            PhoneFavoriteFragment fragment,
             PhoneFavoritesTileAdapter contactTileAdapter,
             CallLogAdapter callLogAdapter,
             View showAllContactsButton) {
         final Resources resources = context.getResources();
         mContext = context;
+        mFragment = fragment;
         mCallLogPadding = resources.getDimensionPixelSize(R.dimen.recent_call_log_item_padding);
         mContactTileAdapter = contactTileAdapter;
         mCallLogAdapter = callLogAdapter;
diff --git a/src/com/android/dialer/list/PhoneFavoriteRegularRowView.java b/src/com/android/dialer/list/PhoneFavoriteRegularRowView.java
index efe7bdd..b4ad784 100644
--- a/src/com/android/dialer/list/PhoneFavoriteRegularRowView.java
+++ b/src/com/android/dialer/list/PhoneFavoriteRegularRowView.java
@@ -56,7 +56,7 @@
         rowPaddingBottom = resources.getDimensionPixelSize(
                 R.dimen.favorites_row_bottom_padding);
 
-        favoriteContactCard.setBackgroundResource(R.drawable.bottom_border_background);
+        favoriteContactCard.setBackgroundResource(R.drawable.contact_list_item_background);
 
         favoriteContactCard.setPaddingRelative(rowPaddingStart, rowPaddingTop, rowPaddingEnd,
                 rowPaddingBottom);
diff --git a/src/com/android/dialer/list/PhoneFavoriteTileView.java b/src/com/android/dialer/list/PhoneFavoriteTileView.java
index 5a3f4e5..57d258f 100644
--- a/src/com/android/dialer/list/PhoneFavoriteTileView.java
+++ b/src/com/android/dialer/list/PhoneFavoriteTileView.java
@@ -31,6 +31,7 @@
 import com.android.contacts.common.MoreContactUtils;
 import com.android.contacts.common.list.ContactEntry;
 import com.android.contacts.common.list.ContactTileView;
+import com.android.dialer.R;
 import com.android.dialer.list.PhoneFavoritesTileAdapter.ContactTileRow;
 import com.android.dialer.list.PhoneFavoritesTileAdapter.ViewTypes;
 
@@ -47,7 +48,7 @@
     private static final boolean DEBUG = false;
 
     /** Length of all animations in miniseconds. */
-    private static final int ANIMATION_LENGTH = 300;
+    private int mAnimationDuration;
 
     /** The view that holds the front layer of the favorite contact card. */
     private View mFavoriteContactCard;
@@ -66,6 +67,7 @@
 
     public PhoneFavoriteTileView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mAnimationDuration = context.getResources().getInteger(R.integer.fade_duration);
     }
 
     public ContactTileRow getParentRow() {
@@ -131,7 +133,7 @@
         mRemovalDialogue.setVisibility(VISIBLE);
         mRemovalDialogue.setAlpha(0f);
         final ObjectAnimator fadeIn = ObjectAnimator.ofFloat(mRemovalDialogue, "alpha",
-                1.f).setDuration(ANIMATION_LENGTH);
+                1.f).setDuration(mAnimationDuration);
 
         fadeIn.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -157,9 +159,9 @@
 
         // Animates back the favorite contact card.
         final ObjectAnimator fadeIn = ObjectAnimator.ofFloat(mFavoriteContactCard, "alpha", 1.f).
-                setDuration(ANIMATION_LENGTH);
+                setDuration(mAnimationDuration);
         final ObjectAnimator moveBack = ObjectAnimator.ofFloat(mFavoriteContactCard, "translationX",
-                0.f).setDuration(ANIMATION_LENGTH);
+                0.f).setDuration(mAnimationDuration);
 
         final AnimatorSet animSet = new AnimatorSet();
 
diff --git a/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java b/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java
index 779b15d..73d3c36 100644
--- a/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java
+++ b/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java
@@ -609,7 +609,8 @@
     public void handleDrop() {
         boolean changed = false;
         if (mDraggedEntry != null) {
-            if (isIndexInBound(mDragEnteredEntryIndex)) {
+            if (isIndexInBound(mDragEnteredEntryIndex) &&
+                    mDragEnteredEntryIndex != mDraggedEntryIndex) {
                 // Don't add the ContactEntry here (to prevent a double animation from occuring).
                 // When we receive a new cursor the list of contact entries will automatically be
                 // populated with the dragged ContactEntry at the correct spot.
@@ -634,7 +635,6 @@
                         PinnedPositions.STAR_WHEN_PINNING, "true").build();
                 // update the database here with the new pinned positions
                 mContext.getContentResolver().update(pinUri, cv, null, null);
-                notifyDataSetChanged();
             }
             mDraggedEntry = null;
         }