From f4b484485a4519a99d797bd9c0c1cc902cfc7414 Mon Sep 17 00:00:00 2001 From: linyuh Date: Thu, 22 Mar 2018 17:39:29 -0700 Subject: Correctly display phone numbers containing whitespaces in RTL context. Bug: 74421656 Test: DialerBidiFormatterTest PiperOrigin-RevId: 190154072 Change-Id: Ic7cb3be702dd28b07b6e5e1e6d89f75f0bb12655 --- .../contacts/common/list/ContactTileView.java | 6 +- .../dialer/app/calllog/PhoneCallDetailsViews.java | 9 +- .../app/list/PhoneFavoriteSquareTileView.java | 10 +- .../dialer/app/res/layout/call_log_list_item.xml | 2 +- .../app/res/layout/phone_favorite_tile_view.xml | 2 +- .../calldetails/CallDetailsHeaderViewHolder.java | 3 +- .../calldetails/res/layout/contact_container.xml | 2 +- .../dialer/contactsfragment/ContactViewHolder.java | 3 +- .../contactsfragment/res/layout/contact_row.xml | 2 +- .../android/dialer/i18n/DialerBidiFormatter.java | 123 +++++++++++++++++++++ .../common/res/layout/search_contact_row.xml | 4 +- .../cp2/SearchContactViewHolder.java | 6 +- java/com/android/dialer/widget/BidiTextView.java | 40 +++++++ 13 files changed, 189 insertions(+), 23 deletions(-) create mode 100644 java/com/android/dialer/i18n/DialerBidiFormatter.java create mode 100644 java/com/android/dialer/widget/BidiTextView.java (limited to 'java/com/android') diff --git a/java/com/android/contacts/common/list/ContactTileView.java b/java/com/android/contacts/common/list/ContactTileView.java index cfd52f385..072e07dd2 100644 --- a/java/com/android/contacts/common/list/ContactTileView.java +++ b/java/com/android/contacts/common/list/ContactTileView.java @@ -22,7 +22,6 @@ import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.TextView; import com.android.contacts.common.MoreContactUtils; import com.android.contacts.common.R; import com.android.dialer.callintent.CallInitiationType; @@ -30,6 +29,7 @@ import com.android.dialer.callintent.CallSpecificAppData; import com.android.dialer.common.LogUtil; import com.android.dialer.contactphoto.ContactPhotoManager; import com.android.dialer.contactphoto.ContactPhotoManager.DefaultImageRequest; +import com.android.dialer.widget.BidiTextView; /** A ContactTile displays a contact's picture and name */ public abstract class ContactTileView extends FrameLayout { @@ -38,7 +38,7 @@ public abstract class ContactTileView extends FrameLayout { protected Listener mListener; private Uri mLookupUri; private ImageView mPhoto; - private TextView mName; + private BidiTextView mName; private ContactPhotoManager mPhotoManager = null; public ContactTileView(Context context, AttributeSet attrs) { @@ -48,7 +48,7 @@ public abstract class ContactTileView extends FrameLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); - mName = (TextView) findViewById(R.id.contact_tile_name); + mName = (BidiTextView) findViewById(R.id.contact_tile_name); mPhoto = (ImageView) findViewById(R.id.contact_tile_image); OnClickListener listener = createClickListener(); diff --git a/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java b/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java index 8b7a92bd4..71cbc8c12 100644 --- a/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java +++ b/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java @@ -21,11 +21,12 @@ import android.view.View; import android.widget.TextView; import com.android.dialer.app.R; import com.android.dialer.calllogutils.CallTypeIconsView; +import com.android.dialer.widget.BidiTextView; /** Encapsulates the views that are used to display the details of a phone call in the call log. */ public final class PhoneCallDetailsViews { - public final TextView nameView; + public final BidiTextView nameView; public final View callTypeView; public final CallTypeIconsView callTypeIcons; public final TextView callLocationAndDate; @@ -36,7 +37,7 @@ public final class PhoneCallDetailsViews { public final TextView callAccountLabel; private PhoneCallDetailsViews( - TextView nameView, + BidiTextView nameView, View callTypeView, CallTypeIconsView callTypeIcons, TextView callLocationAndDate, @@ -65,7 +66,7 @@ public final class PhoneCallDetailsViews { */ public static PhoneCallDetailsViews fromView(View view) { return new PhoneCallDetailsViews( - (TextView) view.findViewById(R.id.name), + (BidiTextView) view.findViewById(R.id.name), view.findViewById(R.id.call_type), (CallTypeIconsView) view.findViewById(R.id.call_type_icons), (TextView) view.findViewById(R.id.call_location_and_date), @@ -78,7 +79,7 @@ public final class PhoneCallDetailsViews { public static PhoneCallDetailsViews createForTest(Context context) { return new PhoneCallDetailsViews( - new TextView(context), + new BidiTextView(context), new View(context), new CallTypeIconsView(context), new TextView(context), diff --git a/java/com/android/dialer/app/list/PhoneFavoriteSquareTileView.java b/java/com/android/dialer/app/list/PhoneFavoriteSquareTileView.java index 330a3614c..6096ca872 100644 --- a/java/com/android/dialer/app/list/PhoneFavoriteSquareTileView.java +++ b/java/com/android/dialer/app/list/PhoneFavoriteSquareTileView.java @@ -28,12 +28,11 @@ import com.android.dialer.app.R; import com.android.dialer.compat.CompatUtils; import com.android.dialer.logging.InteractionEvent; import com.android.dialer.logging.Logger; +import com.android.dialer.widget.BidiTextView; /** Displays the contact's picture overlaid with their name and number type in a tile. */ public class PhoneFavoriteSquareTileView extends PhoneFavoriteTileView { - private static final String TAG = PhoneFavoriteSquareTileView.class.getSimpleName(); - private final float heightToWidthRatio; private ImageButton secondaryButton; @@ -50,11 +49,12 @@ public class PhoneFavoriteSquareTileView extends PhoneFavoriteTileView { @Override protected void onFinishInflate() { super.onFinishInflate(); - final TextView nameView = (TextView) findViewById(R.id.contact_tile_name); + BidiTextView nameView = findViewById(R.id.contact_tile_name); nameView.setElegantTextHeight(false); - final TextView phoneTypeView = (TextView) findViewById(R.id.contact_tile_phone_type); + + TextView phoneTypeView = findViewById(R.id.contact_tile_phone_type); phoneTypeView.setElegantTextHeight(false); - secondaryButton = (ImageButton) findViewById(R.id.contact_tile_secondary_button); + secondaryButton = findViewById(R.id.contact_tile_secondary_button); } @Override diff --git a/java/com/android/dialer/app/res/layout/call_log_list_item.xml b/java/com/android/dialer/app/res/layout/call_log_list_item.xml index acaa82085..d1111103e 100644 --- a/java/com/android/dialer/app/res/layout/call_log_list_item.xml +++ b/java/com/android/dialer/app/res/layout/call_log_list_item.xml @@ -93,7 +93,7 @@ android:gravity="center_vertical" android:layout_marginStart="@dimen/call_log_list_item_info_margin_start"> - - - - Formatted phone numbers usually contain one or more whitespaces (e.g., "+1 650-253-0000", + * "(650) 253-0000", etc). {@link BidiFormatter} mistakes such a number for tokens separated by + * whitespaces. Therefore, these numbers can't be correctly shown in a RTL context (e.g., "+1 + * 650-253-0000" would be shown as "650-253-0000 1+".) + */ +public final class DialerBidiFormatter { + + private DialerBidiFormatter() {} + + /** + * Divides the given text into segments, applies {@link BidiFormatter#unicodeWrap(CharSequence)} + * to each segment, and then reassembles the text. + * + *

A segment of the text is either a substring matching {@link Patterns#PHONE} or one that does + * not. + * + * @see BidiFormatter#unicodeWrap(CharSequence) + */ + public static CharSequence unicodeWrap(@Nullable CharSequence text) { + if (TextUtils.isEmpty(text)) { + return text; + } + + List segments = segmentText(text); + + StringBuilder formattedText = new StringBuilder(); + for (CharSequence segment : segments) { + formattedText.append(BidiFormatter.getInstance().unicodeWrap(segment)); + } + + return formattedText.toString(); + } + + /** + * Segments the given text using {@link Patterns#PHONE}. + * + *

For example, "Mobile, +1 650-253-0000, 20 seconds" will be segmented into {"Mobile, ", "+1 + * 650-253-0000", ", 20 seconds"}. + */ + @VisibleForTesting + static List segmentText(CharSequence text) { + Assert.checkArgument(!TextUtils.isEmpty(text)); + + List segments = new ArrayList<>(); + + // Find the start index and the end index of each segment matching the phone number pattern. + Matcher matcher = Patterns.PHONE.matcher(text.toString()); + List segmentRanges = new ArrayList<>(); + while (matcher.find()) { + segmentRanges.add(Range.newBuilder().setStart(matcher.start()).setEnd(matcher.end()).build()); + } + + // Segment the text. + int currIndex = 0; + for (Range segmentRange : segmentRanges) { + if (currIndex < segmentRange.getStart()) { + segments.add(text.subSequence(currIndex, segmentRange.getStart())); + } + + segments.add(text.subSequence(segmentRange.getStart(), segmentRange.getEnd())); + currIndex = segmentRange.getEnd(); + } + if (currIndex < text.length()) { + segments.add(text.subSequence(currIndex, text.length())); + } + + return segments; + } + + /** Represents the start index (inclusive) and the end index (exclusive) of a text segment. */ + @AutoValue + abstract static class Range { + static Builder newBuilder() { + return new AutoValue_DialerBidiFormatter_Range.Builder(); + } + + abstract int getStart(); + + abstract int getEnd(); + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setStart(int start); + + abstract Builder setEnd(int end); + + abstract Range build(); + } + } +} diff --git a/java/com/android/dialer/searchfragment/common/res/layout/search_contact_row.xml b/java/com/android/dialer/searchfragment/common/res/layout/search_contact_row.xml index 9be7fa046..a0d9dd274 100644 --- a/java/com/android/dialer/searchfragment/common/res/layout/search_contact_row.xml +++ b/java/com/android/dialer/searchfragment/common/res/layout/search_contact_row.xml @@ -39,7 +39,7 @@ android:layout_centerVertical="true" android:layout_marginStart="8dp"> - -