summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/searchfragment/common
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/searchfragment/common')
-rw-r--r--java/com/android/dialer/searchfragment/common/AndroidManifest.xml16
-rw-r--r--java/com/android/dialer/searchfragment/common/Projections.java52
-rw-r--r--java/com/android/dialer/searchfragment/common/QueryBoldingUtil.java151
-rw-r--r--java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java167
-rw-r--r--java/com/android/dialer/searchfragment/common/RowClickListener.java43
-rw-r--r--java/com/android/dialer/searchfragment/common/SearchCursor.java38
-rw-r--r--java/com/android/dialer/searchfragment/common/res/layout/search_contact_row.xml69
-rw-r--r--java/com/android/dialer/searchfragment/common/res/values/dimens.xml22
8 files changed, 558 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..078c3e5e6
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/common/Projections.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+ public static final int PHONE_CONTACT_ID = 9;
+
+ @SuppressWarnings("unused")
+ public static final int PHONE_SORT_KEY = 10;
+
+ 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.CONTACT_ID, // 9
+ Phone.SORT_KEY_PRIMARY // 10
+ };
+}
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..4413252f4
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/common/QueryBoldingUtil.java
@@ -0,0 +1,151 @@
+/*
+ * 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;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** 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 of matches:
+ *
+ * <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>
+ *
+ * <p>Some examples of non-matches:
+ *
+ * <ul>
+ * <li>"ss" would not match "Jessica Jones"
+ * <li>"77" would not match "Jessica Jones"
+ * </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;
+ }
+
+ if (!QueryFilteringUtil.nameMatchesT9Query(query, name)) {
+ Pattern pattern = Pattern.compile("(^|\\s)" + Pattern.quote(query.toLowerCase()));
+ Matcher matcher = pattern.matcher(name.toLowerCase());
+ if (matcher.find()) {
+ // query matches the start of a name (i.e. "jo" -> "Jessica [Jo]nes")
+ return getBoldedString(name, matcher.start(), query.length());
+ } else {
+ // query not found in name
+ return name;
+ }
+ }
+
+ Pattern pattern = Pattern.compile("(^|\\s)" + Pattern.quote(query.toLowerCase()));
+ Matcher matcher = pattern.matcher(QueryFilteringUtil.getT9Representation(name));
+ if (matcher.find()) {
+ // query matches the start of a T9 name (i.e. 75 -> "Jessica [Jo]nes")
+ int index = matcher.start();
+ // TODO(calderwoodra): investigate why this is consistently off by one.
+ index = index == 0 ? 0 : index + 1;
+ return getBoldedString(name, index, query.length());
+
+ } else {
+ // query match the T9 initials (i.e. 222 -> "[A]l [B]ob [C]harlie")
+ return getNameWithInitialsBolded(query, name);
+ }
+ }
+
+ 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..775f8deec
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java
@@ -0,0 +1,167 @@
+/*
+ * 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. */
+ private static final Pattern T9_PATTERN = Pattern.compile("[\\-()2-9]+");
+
+ /**
+ * Returns true if the query is of T9 format and the name's T9 representation belongs to the query
+ *
+ * <p>Examples:
+ *
+ * <ul>
+ * <li>#nameMatchesT9Query("7", "John Smith") returns true, 7 -> 'S'
+ * <li>#nameMatchesT9Query("55", "Jessica Jones") returns true, 55 -> 'JJ'
+ * <li>#nameMatchesT9Query("56", "Jessica Jones") returns true, 56 -> 'Jo'
+ * <li>#nameMatchesT9Query("7", "Jessica Jones") returns false, no names start with P,Q,R or S
+ * </ul>
+ */
+ public static boolean nameMatchesT9Query(String query, String name) {
+ if (!T9_PATTERN.matcher(query).matches()) {
+ return false;
+ }
+
+ query = digitsOnly(query);
+ Pattern pattern = Pattern.compile("(^|\\s)" + Pattern.quote(query));
+ if (pattern.matcher(getT9Representation(name)).find()) {
+ // query matches the start of a T9 name (i.e. 75 -> "Jessica [Jo]nes")
+ return true;
+ }
+
+ // Check matches initials
+ // TODO(calderwoodra) investigate faster implementation
+ 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();
+ }
+
+ /**
+ * Returns true if the subparts of the name (split by white space) begin with the query.
+ *
+ * <p>Examples:
+ *
+ * <ul>
+ * <li>#nameContainsQuery("b", "Brandon") returns true
+ * <li>#nameContainsQuery("o", "Bob") returns false
+ * <li>#nameContainsQuery("o", "Bob Olive") returns true
+ * </ul>
+ */
+ public static boolean nameContainsQuery(String query, String name) {
+ return Pattern.compile("(^|\\s)" + Pattern.quote(query.toLowerCase()))
+ .matcher(name.toLowerCase())
+ .find();
+ }
+
+ /** @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/RowClickListener.java b/java/com/android/dialer/searchfragment/common/RowClickListener.java
new file mode 100644
index 000000000..e82f3f7bb
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/common/RowClickListener.java
@@ -0,0 +1,43 @@
+/*
+ * 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 com.android.dialer.dialercontact.DialerContact;
+
+/** Interface of possible actions that can be performed by search elements. */
+public interface RowClickListener {
+
+ /**
+ * Places a traditional voice call.
+ *
+ * @param ranking position in the list relative to the other elements
+ */
+ void placeVoiceCall(String phoneNumber, int ranking);
+
+ /**
+ * Places an IMS video call.
+ *
+ * @param ranking position in the list relative to the other elements
+ */
+ void placeVideoCall(String phoneNumber, int ranking);
+
+ /** Places a Duo video call. */
+ void placeDuoCall(String phoneNumber);
+
+ /** Opens the enriched calling/call composer interface. */
+ void openCallAndShare(DialerContact dialerContact);
+}
diff --git a/java/com/android/dialer/searchfragment/common/SearchCursor.java b/java/com/android/dialer/searchfragment/common/SearchCursor.java
new file mode 100644
index 000000000..368ee09d6
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/common/SearchCursor.java
@@ -0,0 +1,38 @@
+/*
+ * 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.database.Cursor;
+import android.support.annotation.NonNull;
+
+/** Base cursor interface needed for all cursors used in search. */
+public interface SearchCursor extends Cursor {
+
+ String[] HEADER_PROJECTION = {"header_text"};
+
+ int HEADER_TEXT_POSITION = 0;
+
+ /** Returns true if the current cursor position is a header */
+ boolean isHeader();
+
+ /**
+ * Notifies the cursor that the query has updated.
+ *
+ * @return true if the data set has changed.
+ */
+ boolean updateQuery(@NonNull String query);
+}
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..f6664163c
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/common/res/values/dimens.xml
@@ -0,0 +1,22 @@
+<?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>
+</resources> \ No newline at end of file