diff options
Diffstat (limited to 'java/com/android/dialer/searchfragment/common')
6 files changed, 453 insertions, 0 deletions
diff --git a/java/com/android/dialer/searchfragment/common/AndroidManifest.xml b/java/com/android/dialer/searchfragment/common/AndroidManifest.xml new file mode 100644 index 000000000..178cd83c3 --- /dev/null +++ b/java/com/android/dialer/searchfragment/common/AndroidManifest.xml @@ -0,0 +1,16 @@ +<!-- + ~ Copyright (C) 2017 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 + --> +<manifest package="com.android.dialer.searchfragment.common"/>
\ No newline at end of file diff --git a/java/com/android/dialer/searchfragment/common/Projections.java b/java/com/android/dialer/searchfragment/common/Projections.java new file mode 100644 index 000000000..37e20d195 --- /dev/null +++ b/java/com/android/dialer/searchfragment/common/Projections.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 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.searchfragment.common; + +import android.provider.ContactsContract.CommonDataKinds.Phone; + +/** Class containing relevant projections for searching contacts. */ +public class Projections { + + public static final int PHONE_ID = 0; + public static final int PHONE_TYPE = 1; + public static final int PHONE_LABEL = 2; + public static final int PHONE_NUMBER = 3; + public static final int PHONE_DISPLAY_NAME = 4; + public static final int PHONE_PHOTO_ID = 5; + public static final int PHONE_PHOTO_URI = 6; + public static final int PHONE_LOOKUP_KEY = 7; + public static final int PHONE_CARRIER_PRESENCE = 8; + + @SuppressWarnings("unused") + public static final int PHONE_SORT_KEY = 9; + + public static final String[] PHONE_PROJECTION = + new String[] { + Phone._ID, // 0 + Phone.TYPE, // 1 + Phone.LABEL, // 2 + Phone.NUMBER, // 3 + Phone.DISPLAY_NAME_PRIMARY, // 4 + Phone.PHOTO_ID, // 5 + Phone.PHOTO_THUMBNAIL_URI, // 6 + Phone.LOOKUP_KEY, // 7 + Phone.CARRIER_PRESENCE, // 8 + Phone.SORT_KEY_PRIMARY // 9 + }; +} diff --git a/java/com/android/dialer/searchfragment/common/QueryBoldingUtil.java b/java/com/android/dialer/searchfragment/common/QueryBoldingUtil.java new file mode 100644 index 000000000..7bdd69567 --- /dev/null +++ b/java/com/android/dialer/searchfragment/common/QueryBoldingUtil.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2017 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.searchfragment.common; + +import android.graphics.Typeface; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.StyleSpan; + +/** Utility class for handling bolding queries contained in string. */ +public class QueryBoldingUtil { + + /** + * Compares a name and query and returns a {@link CharSequence} with bolded characters. + * + * <p>Some example: + * + * <ul> + * <li>"query" would bold "John [query] Smith" + * <li>"222" would bold "[AAA] Mom" + * <li>"222" would bold "[A]llen [A]lex [A]aron" + * </ul> + * + * @param query containing any characters + * @param name of a contact/string that query will compare to + * @return name with query bolded if query can be found in the name. + */ + public static CharSequence getNameWithQueryBolded(@Nullable String query, @NonNull String name) { + if (TextUtils.isEmpty(query)) { + return name; + } + + int index = -1; + int numberOfBoldedCharacters = 0; + + if (QueryFilteringUtil.nameMatchesT9Query(query, name)) { + // Bold the characters that match the t9 query + String t9 = QueryFilteringUtil.getT9Representation(name); + index = QueryFilteringUtil.indexOfQueryNonDigitsIgnored(query, t9); + if (index == -1) { + return getNameWithInitialsBolded(query, name); + } + numberOfBoldedCharacters = query.length(); + + for (int i = 0; i < query.length(); i++) { + char c = query.charAt(i); + if (!Character.isDigit(c)) { + numberOfBoldedCharacters--; + } + } + + for (int i = 0; i < index + numberOfBoldedCharacters; i++) { + if (!Character.isLetterOrDigit(name.charAt(i))) { + if (i < index) { + index++; + } else { + numberOfBoldedCharacters++; + } + } + } + } + + if (index == -1) { + // Bold the query as an exact match in the name + index = name.toLowerCase().indexOf(query); + numberOfBoldedCharacters = query.length(); + } + + return index == -1 ? name : getBoldedString(name, index, numberOfBoldedCharacters); + } + + private static CharSequence getNameWithInitialsBolded(String query, String name) { + SpannableString boldedInitials = new SpannableString(name); + name = name.toLowerCase(); + int initialsBolded = 0; + int nameIndex = -1; + + while (++nameIndex < name.length() && initialsBolded < query.length()) { + if ((nameIndex == 0 || name.charAt(nameIndex - 1) == ' ') + && QueryFilteringUtil.getDigit(name.charAt(nameIndex)) == query.charAt(initialsBolded)) { + boldedInitials.setSpan( + new StyleSpan(Typeface.BOLD), + nameIndex, + nameIndex + 1, + Spanned.SPAN_INCLUSIVE_INCLUSIVE); + initialsBolded++; + } + } + return boldedInitials; + } + + /** + * Compares a number and a query and returns a {@link CharSequence} with bolded characters. + * + * <ul> + * <li>"123" would bold "(650)34[1-23]24" + * <li>"123" would bold "+1([123])111-2222 + * </ul> + * + * @param query containing only numbers and phone number related characters "(", ")", "-", "+" + * @param number phone number of a contact that the query will compare to. + * @return number with query bolded if query can be found in the number. + */ + public static CharSequence getNumberWithQueryBolded( + @Nullable String query, @NonNull String number) { + if (TextUtils.isEmpty(query) || !QueryFilteringUtil.numberMatchesNumberQuery(query, number)) { + return number; + } + + int index = QueryFilteringUtil.indexOfQueryNonDigitsIgnored(query, number); + int boldedCharacters = query.length(); + + for (char c : query.toCharArray()) { + if (!Character.isDigit(c)) { + boldedCharacters--; + } + } + + for (int i = 0; i < index + boldedCharacters; i++) { + if (!Character.isDigit(number.charAt(i))) { + if (i <= index) { + index++; + } else { + boldedCharacters++; + } + } + } + return getBoldedString(number, index, boldedCharacters); + } + + private static SpannableString getBoldedString(String s, int index, int numBolded) { + SpannableString span = new SpannableString(s); + span.setSpan( + new StyleSpan(Typeface.BOLD), index, index + numBolded, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + return span; + } +} diff --git a/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java new file mode 100644 index 000000000..b23315b15 --- /dev/null +++ b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2017 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.searchfragment.common; + +import android.support.annotation.NonNull; +import android.telephony.PhoneNumberUtils; +import android.text.TextUtils; +import java.util.regex.Pattern; + +/** Utility class for filtering, comparing and handling strings and queries. */ +public class QueryFilteringUtil { + + /** Matches strings with "-", "(", ")", 2-9 of at least length one. */ + static final Pattern T9_PATTERN = Pattern.compile("[\\-()2-9]+"); + + /** + * @return true if the query is of T9 format and the name's T9 representation belongs to the + * query; false otherwise. + */ + public static boolean nameMatchesT9Query(String query, String name) { + if (!T9_PATTERN.matcher(query).matches()) { + return false; + } + + // Substring + if (indexOfQueryNonDigitsIgnored(query, getT9Representation(name)) != -1) { + return true; + } + + // Check matches initials + // TODO investigate faster implementation + query = digitsOnly(query); + int queryIndex = 0; + + String[] names = name.toLowerCase().split("\\s"); + for (int i = 0; i < names.length && queryIndex < query.length(); i++) { + if (TextUtils.isEmpty(names[i])) { + continue; + } + + if (getDigit(names[i].charAt(0)) == query.charAt(queryIndex)) { + queryIndex++; + } + } + + return queryIndex == query.length(); + } + + /** @return true if the number belongs to the query. */ + public static boolean numberMatchesNumberQuery(String query, String number) { + return PhoneNumberUtils.isGlobalPhoneNumber(query) + && indexOfQueryNonDigitsIgnored(query, number) != -1; + } + + /** + * Checks if query is contained in number while ignoring all characters in both that are not + * digits (i.e. {@link Character#isDigit(char)} returns false). + * + * @return index where query is found with all non-digits removed, -1 if it's not found. + */ + static int indexOfQueryNonDigitsIgnored(@NonNull String query, @NonNull String number) { + return digitsOnly(number).indexOf(digitsOnly(query)); + } + + // Returns string with letters replaced with their T9 representation. + static String getT9Representation(String s) { + StringBuilder builder = new StringBuilder(s.length()); + for (char c : s.toLowerCase().toCharArray()) { + builder.append(getDigit(c)); + } + return builder.toString(); + } + + /** @return String s with only digits recognized by Character#isDigit() remaining */ + public static String digitsOnly(String s) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (Character.isDigit(c)) { + sb.append(c); + } + } + return sb.toString(); + } + + // Returns the T9 representation of a lower case character, otherwise returns the character. + static char getDigit(char c) { + switch (c) { + case 'a': + case 'b': + case 'c': + return '2'; + case 'd': + case 'e': + case 'f': + return '3'; + case 'g': + case 'h': + case 'i': + return '4'; + case 'j': + case 'k': + case 'l': + return '5'; + case 'm': + case 'n': + case 'o': + return '6'; + case 'p': + case 'q': + case 'r': + case 's': + return '7'; + case 't': + case 'u': + case 'v': + return '8'; + case 'w': + case 'x': + case 'y': + case 'z': + return '9'; + default: + return c; + } + } +} 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 new file mode 100644 index 000000000..dd871af70 --- /dev/null +++ b/java/com/android/dialer/searchfragment/common/res/layout/search_contact_row.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 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 + --> +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="@dimen/search_row_height" + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:background="?android:attr/selectableItemBackground"> + + <QuickContactBadge + android:id="@+id/photo" + android:layout_width="@dimen/search_row_height" + android:layout_height="@dimen/search_row_height" + android:padding="@dimen/search_photo_padding" + android:clickable="false"/> + + <LinearLayout + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toEndOf="@+id/photo" + android:layout_toStartOf="@+id/call_to_action" + android:layout_centerVertical="true"> + + <TextView + android:id="@+id/primary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingStart="@dimen/search_text_padding_start" + android:gravity="center_vertical|start" + android:fontFamily="sans-serif" + style="@style/PrimaryText"/> + + <TextView + android:id="@+id/secondary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingStart="@dimen/search_text_padding_start" + android:gravity="center_vertical|start" + android:fontFamily="sans-serif" + style="@style/SecondaryText"/> + </LinearLayout> + + <ImageView + android:id="@+id/call_to_action" + android:layout_width="@dimen/search_row_height" + android:layout_height="@dimen/search_row_height" + android:layout_alignParentEnd="true" + android:padding="@dimen/call_to_action_padding" + android:tint="@color/dialer_secondary_text_color" + android:visibility="gone" + android:scaleType="center"/> +</RelativeLayout>
\ No newline at end of file diff --git a/java/com/android/dialer/searchfragment/common/res/values/dimens.xml b/java/com/android/dialer/searchfragment/common/res/values/dimens.xml new file mode 100644 index 000000000..d5459ddb3 --- /dev/null +++ b/java/com/android/dialer/searchfragment/common/res/values/dimens.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 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 + --> +<resources> + <dimen name="search_row_height">56dp</dimen> + <dimen name="search_photo_padding">8dp</dimen> + <dimen name="call_to_action_padding">8dp</dimen> + <dimen name="search_text_padding_start">16dp</dimen> + <dimen name="new_search_text_size">16sp</dimen> +</resources>
\ No newline at end of file |