1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.dialer.searchfragment.common;
18
19import android.graphics.Typeface;
20import android.support.annotation.NonNull;
21import android.support.annotation.Nullable;
22import android.text.SpannableString;
23import android.text.Spanned;
24import android.text.TextUtils;
25import android.text.style.StyleSpan;
26
27/** Utility class for handling bolding queries contained in string. */
28public class QueryBoldingUtil {
29
30  /**
31   * Compares a name and query and returns a {@link CharSequence} with bolded characters.
32   *
33   * <p>Some example:
34   *
35   * <ul>
36   *   <li>"query" would bold "John [query] Smith"
37   *   <li>"222" would bold "[AAA] Mom"
38   *   <li>"222" would bold "[A]llen [A]lex [A]aron"
39   * </ul>
40   *
41   * @param query containing any characters
42   * @param name of a contact/string that query will compare to
43   * @return name with query bolded if query can be found in the name.
44   */
45  public static CharSequence getNameWithQueryBolded(@Nullable String query, @NonNull String name) {
46    if (TextUtils.isEmpty(query)) {
47      return name;
48    }
49
50    int index = -1;
51    int numberOfBoldedCharacters = 0;
52
53    if (QueryFilteringUtil.nameMatchesT9Query(query, name)) {
54      // Bold the characters that match the t9 query
55      String t9 = QueryFilteringUtil.getT9Representation(name);
56      index = QueryFilteringUtil.indexOfQueryNonDigitsIgnored(query, t9);
57      if (index == -1) {
58        return getNameWithInitialsBolded(query, name);
59      }
60      numberOfBoldedCharacters = query.length();
61
62      for (int i = 0; i < query.length(); i++) {
63        char c = query.charAt(i);
64        if (!Character.isDigit(c)) {
65          numberOfBoldedCharacters--;
66        }
67      }
68
69      for (int i = 0; i < index + numberOfBoldedCharacters; i++) {
70        if (!Character.isLetterOrDigit(name.charAt(i))) {
71          if (i < index) {
72            index++;
73          } else {
74            numberOfBoldedCharacters++;
75          }
76        }
77      }
78    }
79
80    if (index == -1) {
81      // Bold the query as an exact match in the name
82      index = name.toLowerCase().indexOf(query);
83      numberOfBoldedCharacters = query.length();
84    }
85
86    return index == -1 ? name : getBoldedString(name, index, numberOfBoldedCharacters);
87  }
88
89  private static CharSequence getNameWithInitialsBolded(String query, String name) {
90    SpannableString boldedInitials = new SpannableString(name);
91    name = name.toLowerCase();
92    int initialsBolded = 0;
93    int nameIndex = -1;
94
95    while (++nameIndex < name.length() && initialsBolded < query.length()) {
96      if ((nameIndex == 0 || name.charAt(nameIndex - 1) == ' ')
97          && QueryFilteringUtil.getDigit(name.charAt(nameIndex)) == query.charAt(initialsBolded)) {
98        boldedInitials.setSpan(
99            new StyleSpan(Typeface.BOLD),
100            nameIndex,
101            nameIndex + 1,
102            Spanned.SPAN_INCLUSIVE_INCLUSIVE);
103        initialsBolded++;
104      }
105    }
106    return boldedInitials;
107  }
108
109  /**
110   * Compares a number and a query and returns a {@link CharSequence} with bolded characters.
111   *
112   * <ul>
113   *   <li>"123" would bold "(650)34[1-23]24"
114   *   <li>"123" would bold "+1([123])111-2222
115   * </ul>
116   *
117   * @param query containing only numbers and phone number related characters "(", ")", "-", "+"
118   * @param number phone number of a contact that the query will compare to.
119   * @return number with query bolded if query can be found in the number.
120   */
121  public static CharSequence getNumberWithQueryBolded(
122      @Nullable String query, @NonNull String number) {
123    if (TextUtils.isEmpty(query) || !QueryFilteringUtil.numberMatchesNumberQuery(query, number)) {
124      return number;
125    }
126
127    int index = QueryFilteringUtil.indexOfQueryNonDigitsIgnored(query, number);
128    int boldedCharacters = query.length();
129
130    for (char c : query.toCharArray()) {
131      if (!Character.isDigit(c)) {
132        boldedCharacters--;
133      }
134    }
135
136    for (int i = 0; i < index + boldedCharacters; i++) {
137      if (!Character.isDigit(number.charAt(i))) {
138        if (i <= index) {
139          index++;
140        } else {
141          boldedCharacters++;
142        }
143      }
144    }
145    return getBoldedString(number, index, boldedCharacters);
146  }
147
148  private static SpannableString getBoldedString(String s, int index, int numBolded) {
149    SpannableString span = new SpannableString(s);
150    span.setSpan(
151        new StyleSpan(Typeface.BOLD), index, index + numBolded, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
152    return span;
153  }
154}
155