blob: 4ebaa666c79188c02949ec3e46873cf4869050a1 [file] [log] [blame]
/*
* 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();
}
}
}