Merge "Fix IllegalStateException in Dialer" into lmp-dev
diff --git a/res/layout/call_detail_history_header.xml b/res/layout/call_detail_history_header.xml
index 04d406f..def1fd8 100644
--- a/res/layout/call_detail_history_header.xml
+++ b/res/layout/call_detail_history_header.xml
@@ -17,7 +17,7 @@
 <TextView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:ex="http://schemas.android.com/apk/res-auto"
-    android:layout_width="wrap_content"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:paddingTop="@dimen/call_detail_header_top_margin"
     android:paddingBottom="@dimen/call_detail_header_bottom_margin"
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index ea0e130..fd0d66c 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -121,7 +121,7 @@
     <string name="description_video_call" msgid="2933838090743214204">"Videoopkald."</string>
     <string name="description_send_text_message" msgid="7803126439934046891">"Send sms til <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="description_call_log_unheard_voicemail" msgid="118101684236996786">"Uaflyttet besked på telefonsvareren"</string>
-    <string name="description_start_voice_search" msgid="520539488194946012">"Start stemmesøgning"</string>
+    <string name="description_start_voice_search" msgid="520539488194946012">"Start talesøgning"</string>
     <string name="menu_callNumber" msgid="997146291983360266">"Ring til <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="unknown" msgid="740067747858270469">"Ukendte"</string>
     <string name="voicemail" msgid="3851469869202611441">"Telefonsvarer"</string>
@@ -139,7 +139,7 @@
     <string name="simContacts_emptyLoading" msgid="6700035985448642408">"Indlæser fra SIM-kort ..."</string>
     <string name="simContacts_title" msgid="27341688347689769">"Kontakter på SIM-kort"</string>
     <string name="add_contact_not_available" msgid="2731922990890769322">"Genaktiver applikationen Kontaktpersoner for at bruge denne funktion."</string>
-    <string name="voice_search_not_available" msgid="7580616740587850828">"Stemmesøgning er ikke tilgængeligt."</string>
+    <string name="voice_search_not_available" msgid="7580616740587850828">"Talesøgning er ikke tilgængeligt."</string>
     <string name="call_not_available" msgid="8941576511946492225">"Det er ikke muligt at foretage et telefonopkald, fordi applikationen Telefon er deaktiveret."</string>
     <string name="activity_not_available" msgid="8265265380537872585">"Der er ingen installerede apps til at håndtere den valgte handling."</string>
     <string name="dialer_hint_find_contact" msgid="8798845521253672403">"Skriv navn eller telefonnummer"</string>
diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java
index 32d61a2..e2ab130 100644
--- a/src/com/android/dialer/CallDetailActivity.java
+++ b/src/com/android/dialer/CallDetailActivity.java
@@ -32,6 +32,8 @@
 import android.provider.VoicemailContract.Voicemails;
 import android.telecom.PhoneAccount;
 import android.telephony.TelephonyManager;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -134,6 +136,7 @@
     private LinearLayout mVoicemailHeader;
 
     private Uri mVoicemailUri;
+    private BidiFormatter mBidiFormatter = BidiFormatter.getInstance();
 
     /** Whether we should show "edit number before call" in the options menu. */
     private boolean mHasEditNumberBeforeCallOption;
@@ -425,24 +428,25 @@
 
                 final CharSequence callLocationOrType = getNumberTypeOrLocation(firstDetails);
 
-                final CharSequence displayNumber =
-                        mPhoneNumberHelper.getDisplayNumber(
-                                firstDetails.number,
-                                firstDetails.numberPresentation,
-                                firstDetails.formattedNumber);
+                final CharSequence displayNumber = mPhoneNumberHelper.getDisplayNumber(
+                        firstDetails.number,
+                        firstDetails.numberPresentation,
+                        firstDetails.formattedNumber);
+                final String displayNumberStr = mBidiFormatter.unicodeWrap(
+                        displayNumber.toString(), TextDirectionHeuristics.LTR);
+
 
                 if (!TextUtils.isEmpty(firstDetails.name)) {
                     mCallerName.setText(firstDetails.name);
-                    mCallerNumber.setText(callLocationOrType + " " + displayNumber);
+                    mCallerNumber.setText(callLocationOrType + " " + displayNumberStr);
                 } else {
-                    mCallerName.setText(displayNumber);
+                    mCallerName.setText(displayNumberStr);
                     if (!TextUtils.isEmpty(callLocationOrType)) {
                         mCallerNumber.setText(callLocationOrType);
                         mCallerNumber.setVisibility(View.VISIBLE);
                     } else {
                         mCallerNumber.setVisibility(View.GONE);
                     }
-
                 }
 
                 if (!TextUtils.isEmpty(firstDetails.accountLabel)) {
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index 8c5b9c5..7071e51 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -135,7 +135,7 @@
 
     private static final int ACTIVITY_REQUEST_CODE_VOICE_SEARCH = 1;
 
-    private FrameLayout parentLayout;
+    private FrameLayout mParentLayout;
 
     /**
      * Fragment containing the dialpad that slides into view
@@ -421,8 +421,8 @@
 
         mSlideOut.setAnimationListener(mSlideOutListener);
 
-        parentLayout = (FrameLayout) findViewById(R.id.dialtacts_mainlayout);
-        parentLayout.setOnDragListener(new LayoutOnDragListener());
+        mParentLayout = (FrameLayout) findViewById(R.id.dialtacts_mainlayout);
+        mParentLayout.setOnDragListener(new LayoutOnDragListener());
         floatingActionButtonContainer.getViewTreeObserver().addOnGlobalLayoutListener(
                 new ViewTreeObserver.OnGlobalLayoutListener() {
                     @Override
@@ -433,7 +433,7 @@
                             return;
                         }
                         observer.removeOnGlobalLayoutListener(this);
-                        int screenWidth = parentLayout.getWidth();
+                        int screenWidth = mParentLayout.getWidth();
                         mFloatingActionButtonController.setScreenWidth(screenWidth);
                         updateFloatingActionButtonControllerAlignment(false /* animate */);
                     }
@@ -933,7 +933,7 @@
             hideDialpadFragment(true, false);
         } else if (isInSearchUi()) {
             exitSearchUi();
-            DialerUtils.hideInputMethod(parentLayout);
+            DialerUtils.hideInputMethod(mParentLayout);
         } else {
             super.onBackPressed();
         }
@@ -945,7 +945,7 @@
     private boolean maybeExitSearchUi() {
         if (isInSearchUi() && TextUtils.isEmpty(mSearchQuery)) {
             exitSearchUi();
-            DialerUtils.hideInputMethod(parentLayout);
+            DialerUtils.hideInputMethod(mParentLayout);
             return true;
         }
         return false;
@@ -982,7 +982,7 @@
     public void onListFragmentScrollStateChange(int scrollState) {
         if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
             hideDialpadFragment(true, false);
-            DialerUtils.hideInputMethod(getCurrentFocus());
+            DialerUtils.hideInputMethod(mParentLayout);
         }
     }
 
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index b585b89..dcd2de3 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -758,7 +758,11 @@
         final PhoneCallDetails details;
 
         views.reported = info.isBadData;
-        views.isExternal = mContactInfoHelper.isExternal(info.sourceType);
+
+        // The entry can only be reported as invalid if it has a valid ID and the source of the
+        // entry supports marking entries as invalid.
+        views.canBeReportedAsInvalid = mContactInfoHelper.canReportAsInvalid(info.sourceType,
+                info.objectId);
 
         // Restore expansion state of the row on rebind.  Inflate the actions ViewStub if required,
         // and set its visibility state accordingly.
@@ -1023,7 +1027,7 @@
                             views.rowId, views.callIds, null)
             );
 
-            if (views.isExternal && !views.reported) {
+            if (views.canBeReportedAsInvalid && !views.reported) {
                 views.reportButtonView.setVisibility(View.VISIBLE);
             } else {
                 views.reportButtonView.setVisibility(View.GONE);
diff --git a/src/com/android/dialer/calllog/CallLogListItemViews.java b/src/com/android/dialer/calllog/CallLogListItemViews.java
index dde4c91..0ccdf00 100644
--- a/src/com/android/dialer/calllog/CallLogListItemViews.java
+++ b/src/com/android/dialer/calllog/CallLogListItemViews.java
@@ -108,9 +108,10 @@
     public boolean reported;
 
     /**
-     * Whether or not the contact info came from a source other than the android contacts provider.
+     * Whether or not the contact info can be marked as invalid from the source where
+     * it was obtained.
      */
-    public boolean isExternal;
+    public boolean canBeReportedAsInvalid;
 
     private CallLogListItemViews(QuickContactBadge quickContactView, View primaryActionView,
             PhoneCallDetailsViews phoneCallDetailsViews, View callLogEntryView,
diff --git a/src/com/android/dialer/calllog/ContactInfo.java b/src/com/android/dialer/calllog/ContactInfo.java
index cf29c5c..7b6014d 100644
--- a/src/com/android/dialer/calllog/ContactInfo.java
+++ b/src/com/android/dialer/calllog/ContactInfo.java
@@ -39,6 +39,7 @@
     /** The high-res photo for the contact, if available. */
     public Uri photoUri;
     public boolean isBadData;
+    public String objectId;
 
     public static ContactInfo EMPTY = new ContactInfo();
 
@@ -73,6 +74,7 @@
         if (!TextUtils.equals(normalizedNumber, other.normalizedNumber)) return false;
         if (photoId != other.photoId) return false;
         if (!UriUtils.areEqual(photoUri, other.photoUri)) return false;
+        if (!TextUtils.equals(objectId, other.objectId)) return false;
         return true;
     }
 
@@ -81,6 +83,6 @@
         return Objects.toStringHelper(this).add("lookupUri", lookupUri).add("name", name).add(
                 "type", type).add("label", label).add("number", number).add("formattedNumber",
                 formattedNumber).add("normalizedNumber", normalizedNumber).add("photoId", photoId)
-                .add("photoUri", photoUri).toString();
+                .add("photoUri", photoUri).add("objectId", objectId).toString();
     }
 }
diff --git a/src/com/android/dialer/calllog/ContactInfoHelper.java b/src/com/android/dialer/calllog/ContactInfoHelper.java
index 1f99a88..01749fc 100644
--- a/src/com/android/dialer/calllog/ContactInfoHelper.java
+++ b/src/com/android/dialer/calllog/ContactInfoHelper.java
@@ -294,14 +294,17 @@
     }
 
     /**
-     * Given a contact's sourceType, return true if the contact came from an
-     * external source.
+     * This function looks at a contact's source and determines if the user can
+     * mark caller ids from this source as invalid.
      *
-     * @param sourceType sourceType of the contact. This is usually populated by
-     *        {@link #mCachedNumberLookupService}.
+     * @param sourceType The source type to be checked
+     * @param objectId The ID of the Contact object.
+     * @return true if contacts from this source can be marked with an invalid caller id
      */
-    public boolean isExternal(int sourceType) {
+    public boolean canReportAsInvalid(int sourceType, String objectId) {
         return mCachedNumberLookupService != null
-                && mCachedNumberLookupService.isExternal(sourceType);
+                && mCachedNumberLookupService.canReportAsInvalid(sourceType, objectId);
     }
+
+
 }
diff --git a/src/com/android/dialer/service/CachedNumberLookupService.java b/src/com/android/dialer/service/CachedNumberLookupService.java
index 2fec45c..a3782f1 100644
--- a/src/com/android/dialer/service/CachedNumberLookupService.java
+++ b/src/com/android/dialer/service/CachedNumberLookupService.java
@@ -34,8 +34,7 @@
     public boolean isCacheUri(String uri);
 
     public boolean isBusiness(int sourceType);
-
-    public boolean isExternal(int sourceType);
+    public boolean canReportAsInvalid(int sourceType, String objectId);
 
     public boolean addPhoto(Context context, String number, byte[] photo);
 
diff --git a/src/com/android/dialer/widget/OverlappingPaneLayout.java b/src/com/android/dialer/widget/OverlappingPaneLayout.java
index ad18b14..95a0e43 100644
--- a/src/com/android/dialer/widget/OverlappingPaneLayout.java
+++ b/src/com/android/dialer/widget/OverlappingPaneLayout.java
@@ -1026,8 +1026,6 @@
 
             if (state == ViewDragHelper.STATE_IDLE
                     && mDragHelper.getVelocityMagnitude() > 0
-                    && (mDragHelper.getCurrentScrollY() == 0
-                    || mDragHelper.getCurrentScrollY() == mIntermediateOffset)
                     && mIsInNestedFling) {
                 mIsInNestedFling = false;
                 final int flingVelocity = !mInUpwardsPreFling ?
diff --git a/src/com/android/dialer/widget/ViewDragHelper.java b/src/com/android/dialer/widget/ViewDragHelper.java
index e4fe12b..a0e1d80 100644
--- a/src/com/android/dialer/widget/ViewDragHelper.java
+++ b/src/com/android/dialer/widget/ViewDragHelper.java
@@ -21,12 +21,12 @@
 import android.support.v4.view.MotionEventCompat;
 import android.support.v4.view.VelocityTrackerCompat;
 import android.support.v4.view.ViewCompat;
-import android.support.v4.widget.ScrollerCompat;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.widget.Scroller;
 
 import java.util.Arrays;
 
@@ -129,7 +129,11 @@
     private int mEdgeSize;
     private int mTrackingEdges;
 
-    private ScrollerCompat mScroller;
+    // We need to use a Scroller instead of an OverScroller (b/17700698) and as a result, we need
+    // to keep track of the final scroll position ourselves in mFinalScrollY (b/17704016) whenever
+    // we programmatically scroll or fling mScroller.
+    private Scroller mScroller;
+    private int mFinalScrollY;
 
     private final Callback mCallback;
 
@@ -390,7 +394,7 @@
         mTouchSlop = vc.getScaledTouchSlop();
         mMaxVelocity = vc.getScaledMaximumFlingVelocity();
         mMinVelocity = vc.getScaledMinimumFlingVelocity();
-        mScroller = ScrollerCompat.create(context);
+        mScroller = new Scroller(context);
     }
 
     /**
@@ -590,6 +594,7 @@
 
         final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);
         mScroller.startScroll(startLeft, startTop, dx, dy, duration);
+        mFinalScrollY = startTop + dy;
 
         setDragState(STATE_SETTLING);
         return true;
@@ -694,10 +699,12 @@
                     "Callback#onViewReleased");
         }
 
+        final int yVelocity = (int) VelocityTrackerCompat
+                .getYVelocity(mVelocityTracker, mActivePointerId);
         mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(),
                 (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
-                (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),
-                minLeft, maxLeft, minTop, maxTop);
+                yVelocity, minLeft, maxLeft, minTop, maxTop);
+        mFinalScrollY = yVelocity < 0 ? minTop : maxTop;
 
         setDragState(STATE_SETTLING);
     }
@@ -719,8 +726,9 @@
                     "Callback#onViewReleased");
         }
         mScroller.abortAnimation();
-        mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(), 0, yvel, minLeft, maxLeft,
-                minTop, maxTop);
+        mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(), 0, yvel,
+                Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
+        mFinalScrollY = yvel < 0 ? minTop : maxTop;
 
         setDragState(STATE_SETTLING);
     }
@@ -738,10 +746,6 @@
         return finalY;
     }
 
-    public int getCurrentScrollY() {
-        return mScroller.getCurrY();
-    }
-
     /**
      * Move the captured settling view by the appropriate amount for the current time.
      * If <code>continueSettling</code> returns true, the caller should call it again
@@ -756,23 +760,23 @@
     public boolean continueSettling(boolean deferCallbacks) {
         if (mDragState == STATE_SETTLING) {
             boolean keepGoing = mScroller.computeScrollOffset();
-            final int x = mScroller.getCurrX();
-            final int y = mScroller.getCurrY();
-            final int dx = x - mCapturedView.getLeft();
+            int y = mScroller.getCurrY();
+
+            // Since Scroller's getFinalY() can't be properly set (b/17704016), we need to
+            // perform clamping of mScroller.getCurrY() here.
+            if (y - mCapturedView.getTop() > 0) {
+                y = Math.min(y, mFinalScrollY);
+            } else {
+                y = Math.max(y, mFinalScrollY);
+            }
             final int dy = y - mCapturedView.getTop();
 
-            if (dx != 0) {
-                mCapturedView.offsetLeftAndRight(dx);
-            }
             if (dy != 0) {
                 mCapturedView.offsetTopAndBottom(dy);
+                mCallback.onViewPositionChanged(mCapturedView, 0, y, 0, dy);
             }
 
-            if (dx != 0 || dy != 0) {
-                mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy);
-            }
-
-            if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) {
+            if (keepGoing && y == mFinalScrollY) {
                 // Close enough. The interpolator/scroller might think we're still moving
                 // but the user sure doesn't.
                 mScroller.abortAnimation();