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 --- .../android/dialer/i18n/DialerBidiFormatter.java | 123 +++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 java/com/android/dialer/i18n/DialerBidiFormatter.java (limited to 'java/com/android/dialer/i18n') 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. + * + *

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(); + } + } +} -- cgit v1.2.3