summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlinyuh <linyuh@google.com>2018-03-22 17:39:29 -0700
committerCopybara-Service <copybara-piper@google.com>2018-03-26 22:16:16 -0700
commitf4b484485a4519a99d797bd9c0c1cc902cfc7414 (patch)
treede75b03528aae36b06fc402fa39d081250671048
parent009695e477f02b13d563f17a8de0e179d7715bf9 (diff)
Correctly display phone numbers containing whitespaces in RTL context.
Bug: 74421656 Test: DialerBidiFormatterTest PiperOrigin-RevId: 190154072 Change-Id: Ic7cb3be702dd28b07b6e5e1e6d89f75f0bb12655
-rw-r--r--java/com/android/contacts/common/list/ContactTileView.java6
-rw-r--r--java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java9
-rw-r--r--java/com/android/dialer/app/list/PhoneFavoriteSquareTileView.java10
-rw-r--r--java/com/android/dialer/app/res/layout/call_log_list_item.xml2
-rw-r--r--java/com/android/dialer/app/res/layout/phone_favorite_tile_view.xml2
-rw-r--r--java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java3
-rw-r--r--java/com/android/dialer/calldetails/res/layout/contact_container.xml2
-rw-r--r--java/com/android/dialer/contactsfragment/ContactViewHolder.java3
-rw-r--r--java/com/android/dialer/contactsfragment/res/layout/contact_row.xml2
-rw-r--r--java/com/android/dialer/i18n/DialerBidiFormatter.java123
-rw-r--r--java/com/android/dialer/searchfragment/common/res/layout/search_contact_row.xml4
-rw-r--r--java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java6
-rw-r--r--java/com/android/dialer/widget/BidiTextView.java40
13 files changed, 189 insertions, 23 deletions
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">
- <TextView
+ <com.android.dialer.widget.BidiTextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/java/com/android/dialer/app/res/layout/phone_favorite_tile_view.xml b/java/com/android/dialer/app/res/layout/phone_favorite_tile_view.xml
index d2712e9fe..3aeba98a7 100644
--- a/java/com/android/dialer/app/res/layout/phone_favorite_tile_view.xml
+++ b/java/com/android/dialer/app/res/layout/phone_favorite_tile_view.xml
@@ -63,7 +63,7 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
- <TextView
+ <com.android.dialer.widget.BidiTextView
android:id="@+id/contact_tile_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
diff --git a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java
index e4fded173..cb84a28c2 100644
--- a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java
+++ b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java
@@ -40,6 +40,7 @@ import com.android.dialer.dialercontact.DialerContact;
import com.android.dialer.glidephotomanager.GlidePhotoManagerComponent;
import com.android.dialer.logging.InteractionEvent;
import com.android.dialer.logging.Logger;
+import com.android.dialer.widget.BidiTextView;
/**
* ViewHolder for the header in {@link OldCallDetailsActivity} or {@link CallDetailsActivity}.
@@ -51,7 +52,7 @@ public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder
private final CallDetailsHeaderListener callDetailsHeaderListener;
private final ImageView callbackButton;
- private final TextView nameView;
+ private final BidiTextView nameView;
private final TextView numberView;
private final TextView networkView;
private final QuickContactBadge contactPhoto;
diff --git a/java/com/android/dialer/calldetails/res/layout/contact_container.xml b/java/com/android/dialer/calldetails/res/layout/contact_container.xml
index 5f531ab43..9506183a0 100644
--- a/java/com/android/dialer/calldetails/res/layout/contact_container.xml
+++ b/java/com/android/dialer/calldetails/res/layout/contact_container.xml
@@ -44,7 +44,7 @@
android:minHeight="@dimen/call_details_contact_photo_size"
android:orientation="vertical">
- <TextView
+ <com.android.dialer.widget.BidiTextView
android:id="@+id/contact_name"
style="@style/PrimaryText"
android:layout_width="wrap_content"
diff --git a/java/com/android/dialer/contactsfragment/ContactViewHolder.java b/java/com/android/dialer/contactsfragment/ContactViewHolder.java
index 2730c0d38..e1883322c 100644
--- a/java/com/android/dialer/contactsfragment/ContactViewHolder.java
+++ b/java/com/android/dialer/contactsfragment/ContactViewHolder.java
@@ -28,12 +28,13 @@ import com.android.dialer.common.Assert;
import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener;
import com.android.dialer.logging.InteractionEvent;
import com.android.dialer.logging.Logger;
+import com.android.dialer.widget.BidiTextView;
/** View holder for a contact. */
final class ContactViewHolder extends RecyclerView.ViewHolder implements OnClickListener {
private final TextView header;
- private final TextView name;
+ private final BidiTextView name;
private final QuickContactBadge photo;
private final Context context;
private final OnContactSelectedListener onContactSelectedListener;
diff --git a/java/com/android/dialer/contactsfragment/res/layout/contact_row.xml b/java/com/android/dialer/contactsfragment/res/layout/contact_row.xml
index 9e829fee4..b65a8c87f 100644
--- a/java/com/android/dialer/contactsfragment/res/layout/contact_row.xml
+++ b/java/com/android/dialer/contactsfragment/res/layout/contact_row.xml
@@ -41,7 +41,7 @@
android:layout_height="@dimen/photo_size"
android:clickable="false"/>
- <TextView
+ <com.android.dialer.widget.BidiTextView
android:id="@+id/contact_name"
android:layout_width="wrap_content"
android:layout_height="match_parent"
diff --git a/java/com/android/dialer/i18n/DialerBidiFormatter.java b/java/com/android/dialer/i18n/DialerBidiFormatter.java
new file mode 100644
index 000000000..4ebaa666c
--- /dev/null
+++ b/java/com/android/dialer/i18n/DialerBidiFormatter.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.i18n;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.text.BidiFormatter;
+import android.text.TextUtils;
+import android.util.Patterns;
+import com.android.dialer.common.Assert;
+import com.google.auto.value.AutoValue;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+
+/**
+ * An enhanced version of {@link BidiFormatter} that can recognize a formatted phone number
+ * containing whitespaces.
+ *
+ * <p>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.
+ *
+ * <p>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<CharSequence> 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}.
+ *
+ * <p>For example, "Mobile, +1 650-253-0000, 20 seconds" will be segmented into {"Mobile, ", "+1
+ * 650-253-0000", ", 20 seconds"}.
+ */
+ @VisibleForTesting
+ static List<CharSequence> segmentText(CharSequence text) {
+ Assert.checkArgument(!TextUtils.isEmpty(text));
+
+ List<CharSequence> 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<Range> 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">
- <TextView
+ <com.android.dialer.widget.BidiTextView
android:id="@+id/primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -47,7 +47,7 @@
android:fontFamily="sans-serif"
style="@style/PrimaryText"/>
- <TextView
+ <com.android.dialer.widget.BidiTextView
android:id="@+id/secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java
index e36df4bf7..9d18e07b1 100644
--- a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java
+++ b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java
@@ -30,7 +30,6 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.QuickContactBadge;
-import android.widget.TextView;
import com.android.dialer.common.Assert;
import com.android.dialer.contactphoto.ContactPhotoManager;
import com.android.dialer.dialercontact.DialerContact;
@@ -44,6 +43,7 @@ import com.android.dialer.searchfragment.common.QueryBoldingUtil;
import com.android.dialer.searchfragment.common.R;
import com.android.dialer.searchfragment.common.RowClickListener;
import com.android.dialer.searchfragment.common.SearchCursor;
+import com.android.dialer.widget.BidiTextView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -67,8 +67,8 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick
private final RowClickListener listener;
private final QuickContactBadge photo;
- private final TextView nameOrNumberView;
- private final TextView numberView;
+ private final BidiTextView nameOrNumberView;
+ private final BidiTextView numberView;
private final ImageView callToActionView;
private final Context context;
diff --git a/java/com/android/dialer/widget/BidiTextView.java b/java/com/android/dialer/widget/BidiTextView.java
new file mode 100644
index 000000000..6cf1aaedf
--- /dev/null
+++ b/java/com/android/dialer/widget/BidiTextView.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.widget;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.widget.TextView;
+import com.android.dialer.i18n.DialerBidiFormatter;
+
+/** A {@link TextView} that applies bidirectional formatting to its text. */
+public final class BidiTextView extends TextView {
+
+ public BidiTextView(Context context) {
+ super(context);
+ }
+
+ public BidiTextView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setText(CharSequence text, BufferType type) {
+ super.setText(DialerBidiFormatter.unicodeWrap(text), type);
+ }
+}