From 83d8a62ee2b2262941590ff5ef35986dc1df1fa1 Mon Sep 17 00:00:00 2001 From: yueg Date: Thu, 2 Nov 2017 13:46:49 -0700 Subject: Commit transaction synchronously for OnHoldFragment. We show OnHoldFragment for new conference call, and remove it after updating info. After cl/172475223, the update only happens once and sometimes removing the fragment happens before adding the fragment. Committing synchronously fix the bug. Test: manual PiperOrigin-RevId: 174374676 Change-Id: I5337f6592ca2924ed6a089ecaefe00d920f85738 --- java/com/android/incallui/incall/impl/InCallFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com/android/incallui/incall/impl/InCallFragment.java b/java/com/android/incallui/incall/impl/InCallFragment.java index fc31c74e2..73b414d46 100644 --- a/java/com/android/incallui/incall/impl/InCallFragment.java +++ b/java/com/android/incallui/incall/impl/InCallFragment.java @@ -320,7 +320,7 @@ public class InCallFragment extends Fragment } } transaction.setCustomAnimations(R.anim.abc_slide_in_top, R.anim.abc_slide_out_top); - transaction.commitAllowingStateLoss(); + transaction.commitNowAllowingStateLoss(); } @Override -- cgit v1.2.3 From 82670fee34bfc922905f31d3904406eb0677b162 Mon Sep 17 00:00:00 2001 From: linyuh Date: Thu, 2 Nov 2017 18:05:33 -0700 Subject: Support dual alphabets in smart search when a secondary alphabet is available. Bug: 30215380 Test: QueryBoldingUtilTest, QueryFilteringUtilTest, ContactFilterCursorTest PiperOrigin-RevId: 174408771 Change-Id: I4c601b16dd90db6b7b2a05c9daa6804749ea2a43 --- .../dialer/dialpadview/DialpadAlphabets.java | 80 --------- .../dialer/dialpadview/DialpadCharMappings.java | 181 +++++++++++++++++++++ .../android/dialer/dialpadview/DialpadView.java | 5 +- .../searchfragment/common/QueryBoldingUtil.java | 17 +- .../searchfragment/common/QueryFilteringUtil.java | 101 +++++++----- .../searchfragment/cp2/ContactFilterCursor.java | 12 +- .../cp2/SearchContactViewHolder.java | 2 +- .../searchfragment/cp2/SearchContactsCursor.java | 10 +- .../cp2/SearchContactsCursorLoader.java | 2 +- .../nearbyplaces/NearbyPlaceViewHolder.java | 4 +- .../remote/RemoteContactViewHolder.java | 4 +- 11 files changed, 269 insertions(+), 149 deletions(-) delete mode 100644 java/com/android/dialer/dialpadview/DialpadAlphabets.java create mode 100644 java/com/android/dialer/dialpadview/DialpadCharMappings.java diff --git a/java/com/android/dialer/dialpadview/DialpadAlphabets.java b/java/com/android/dialer/dialpadview/DialpadAlphabets.java deleted file mode 100644 index f02ca4395..000000000 --- a/java/com/android/dialer/dialpadview/DialpadAlphabets.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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.dialpadview; - -import android.support.v4.util.SimpleArrayMap; - -/** A class containing key-letter mappings for the dialpad. */ -public class DialpadAlphabets { - - // The default mapping (the Latin alphabet) - private static final String[] def = { - "+" /* 0 */, - "" /* 1 */, - "ABC" /* 2 */, - "DEF" /* 3 */, - "GHI" /* 4 */, - "JKL" /* 5 */, - "MNO" /* 6 */, - "PQRS" /* 7 */, - "TUV" /* 8 */, - "WXYZ" /* 9 */, - "" /* * */, - "" /* # */, - }; - - // Russian - private static final String[] rus = { - "" /* 0 */, - "" /* 1 */, - "АБВГ" /* 2 */, - "ДЕЖЗ" /* 3 */, - "ИЙКЛ" /* 4 */, - "МНОП" /* 5 */, - "РСТУ" /* 6 */, - "ФХЦЧ" /* 7 */, - "ШЩЪЫ" /* 8 */, - "ЬЭЮЯ" /* 9 */, - "" /* * */, - "" /* # */, - }; - - // A map in which each key is an ISO 639-2 language code and the corresponding key is an array - // defining key-letter mappings - private static final SimpleArrayMap alphabets = new SimpleArrayMap<>(); - - static { - alphabets.put("rus", rus); - } - - /** - * Returns the alphabet (a key-letter mapping) of the given ISO 639-2 language code or null if - * - *
    - *
  • no alphabet for the language code is defined, or - *
  • the language code is invalid. - *
- */ - public static String[] getAlphabetForLanguage(String languageCode) { - return alphabets.get(languageCode); - } - - /** Returns the default key-letter mapping (the one that uses the Latin alphabet). */ - public static String[] getDefaultAlphabet() { - return def; - } -} diff --git a/java/com/android/dialer/dialpadview/DialpadCharMappings.java b/java/com/android/dialer/dialpadview/DialpadCharMappings.java new file mode 100644 index 000000000..12994bc78 --- /dev/null +++ b/java/com/android/dialer/dialpadview/DialpadCharMappings.java @@ -0,0 +1,181 @@ +/* + * 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.dialpadview; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; +import android.support.v4.util.SimpleArrayMap; +import com.android.dialer.common.Assert; +import com.android.dialer.compat.CompatUtils; +import com.android.dialer.configprovider.ConfigProviderBindings; + +/** A class containing character mappings for the dialpad. */ +public class DialpadCharMappings { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + public static final String FLAG_ENABLE_DUAL_ALPHABETS = "enable_dual_alphabets_on_t9"; + + /** The character mapping for the Latin alphabet (the default mapping) */ + private static class Latin { + private static final String[] KEY_TO_CHARS = { + "+" /* 0 */, + "" /* 1 */, + "ABC" /* 2 */, + "DEF" /* 3 */, + "GHI" /* 4 */, + "JKL" /* 5 */, + "MNO" /* 6 */, + "PQRS" /* 7 */, + "TUV" /* 8 */, + "WXYZ" /* 9 */, + "" /* * */, + "" /* # */, + }; + + private static final SimpleArrayMap CHAR_TO_KEY = + getCharToKeyMap(KEY_TO_CHARS); + } + + /** The character mapping for the Russian alphabet */ + private static class Rus { + private static final String[] KEY_TO_CHARS = { + "" /* 0 */, + "" /* 1 */, + "АБВГ" /* 2 */, + "ДЕЖЗ" /* 3 */, + "ИЙКЛ" /* 4 */, + "МНОП" /* 5 */, + "РСТУ" /* 6 */, + "ФХЦЧ" /* 7 */, + "ШЩЪЫ" /* 8 */, + "ЬЭЮЯ" /* 9 */, + "" /* * */, + "" /* # */, + }; + + private static final SimpleArrayMap CHAR_TO_KEY = + getCharToKeyMap(KEY_TO_CHARS); + } + + // A map in which each key is an ISO 639-2 language code and the corresponding value is a + // character-key map. + private static final SimpleArrayMap> + CHAR_TO_KEY_MAPS = new SimpleArrayMap<>(); + + // A map in which each key is an ISO 639-2 language code and the corresponding value is an array + // defining a key-characters map. + private static final SimpleArrayMap KEY_TO_CHAR_MAPS = new SimpleArrayMap<>(); + + static { + CHAR_TO_KEY_MAPS.put("rus", Rus.CHAR_TO_KEY); + KEY_TO_CHAR_MAPS.put("rus", Rus.KEY_TO_CHARS); + } + + /** + * Returns the character-key map of the ISO 639-2 language code of the 1st language preference or + * null if + * + *
    + *
  • no character-key map for the language code is defined, or + *
  • the support for dual alphabets is disabled. + *
+ */ + public static SimpleArrayMap getCharToKeyMap(@NonNull Context context) { + return isDualAlphabetsEnabled(context) + ? CHAR_TO_KEY_MAPS.get(CompatUtils.getLocale(context).getISO3Language()) + : null; + } + + /** Returns the default character-key map (the one that uses the Latin alphabet). */ + public static SimpleArrayMap getDefaultCharToKeyMap() { + return Latin.CHAR_TO_KEY; + } + + /** + * Returns the key-characters map of the given ISO 639-2 language code of the 1st language + * preference or null if + * + *
    + *
  • no key-characters map for the language code is defined, or + *
  • the support for dual alphabets is disabled. + *
+ */ + public static String[] getKeyToCharsMap(@NonNull Context context) { + return isDualAlphabetsEnabled(context) + ? KEY_TO_CHAR_MAPS.get(CompatUtils.getLocale(context).getISO3Language()) + : null; + } + + /** Returns the default key-characters map (the one that uses the Latin alphabet). */ + public static String[] getDefaultKeyToCharsMap() { + return Latin.KEY_TO_CHARS; + } + + private static boolean isDualAlphabetsEnabled(Context context) { + return ConfigProviderBindings.get(context).getBoolean(FLAG_ENABLE_DUAL_ALPHABETS, false); + } + + /** + * Given a array representing a key-characters map, return its reverse map. + * + *

It is the caller's responsibility to ensure that + * + *

    + *
  • the array contains only 12 elements, + *
  • the 0th element ~ the 9th element are the mappings for keys "0" ~ "9", + *
  • the 10th element is for key "*", and + *
  • the 11th element is for key "#". + *
+ * + * @param keyToChars An array representing a key-characters map. It must satisfy the conditions + * above. + * @return A character-key map. + */ + private static SimpleArrayMap getCharToKeyMap( + @NonNull String[] keyToChars) { + Assert.checkArgument(keyToChars.length == 12); + + SimpleArrayMap charToKeyMap = new SimpleArrayMap<>(); + + for (int keyIndex = 0; keyIndex < keyToChars.length; keyIndex++) { + String chars = keyToChars[keyIndex]; + + for (int j = 0; j < chars.length(); j++) { + char c = chars.charAt(j); + if (Character.isAlphabetic(c)) { + charToKeyMap.put(Character.toLowerCase(c), getKeyChar(keyIndex)); + } + } + } + + return charToKeyMap; + } + + /** Given a key index of the dialpad, returns the corresponding character. */ + private static char getKeyChar(int keyIndex) { + Assert.checkArgument(0 <= keyIndex && keyIndex <= 11); + + switch (keyIndex) { + case 10: + return '*'; + case 11: + return '#'; + default: + return (char) ('0' + keyIndex); + } + } +} diff --git a/java/com/android/dialer/dialpadview/DialpadView.java b/java/com/android/dialer/dialpadview/DialpadView.java index 38ab383a8..5794038ce 100644 --- a/java/com/android/dialer/dialpadview/DialpadView.java +++ b/java/com/android/dialer/dialpadview/DialpadView.java @@ -114,9 +114,8 @@ public class DialpadView extends LinearLayout { mIsRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL; - mPrimaryLettersMapping = DialpadAlphabets.getDefaultAlphabet(); - mSecondaryLettersMapping = - DialpadAlphabets.getAlphabetForLanguage(CompatUtils.getLocale(context).getISO3Language()); + mPrimaryLettersMapping = DialpadCharMappings.getDefaultKeyToCharsMap(); + mSecondaryLettersMapping = DialpadCharMappings.getKeyToCharsMap(context); } @Override diff --git a/java/com/android/dialer/searchfragment/common/QueryBoldingUtil.java b/java/com/android/dialer/searchfragment/common/QueryBoldingUtil.java index 4413252f4..9ac6e7c5e 100644 --- a/java/com/android/dialer/searchfragment/common/QueryBoldingUtil.java +++ b/java/com/android/dialer/searchfragment/common/QueryBoldingUtil.java @@ -16,6 +16,7 @@ package com.android.dialer.searchfragment.common; +import android.content.Context; import android.graphics.Typeface; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -49,14 +50,16 @@ public class QueryBoldingUtil { * * @param query containing any characters * @param name of a contact/string that query will compare to + * @param context of the app * @return name with query bolded if query can be found in the name. */ - public static CharSequence getNameWithQueryBolded(@Nullable String query, @NonNull String name) { + public static CharSequence getNameWithQueryBolded( + @Nullable String query, @NonNull String name, @NonNull Context context) { if (TextUtils.isEmpty(query)) { return name; } - if (!QueryFilteringUtil.nameMatchesT9Query(query, name)) { + if (!QueryFilteringUtil.nameMatchesT9Query(query, name, context)) { Pattern pattern = Pattern.compile("(^|\\s)" + Pattern.quote(query.toLowerCase())); Matcher matcher = pattern.matcher(name.toLowerCase()); if (matcher.find()) { @@ -69,7 +72,7 @@ public class QueryBoldingUtil { } Pattern pattern = Pattern.compile("(^|\\s)" + Pattern.quote(query.toLowerCase())); - Matcher matcher = pattern.matcher(QueryFilteringUtil.getT9Representation(name)); + Matcher matcher = pattern.matcher(QueryFilteringUtil.getT9Representation(name, context)); if (matcher.find()) { // query matches the start of a T9 name (i.e. 75 -> "Jessica [Jo]nes") int index = matcher.start(); @@ -79,11 +82,12 @@ public class QueryBoldingUtil { } else { // query match the T9 initials (i.e. 222 -> "[A]l [B]ob [C]harlie") - return getNameWithInitialsBolded(query, name); + return getNameWithInitialsBolded(query, name, context); } } - private static CharSequence getNameWithInitialsBolded(String query, String name) { + private static CharSequence getNameWithInitialsBolded( + String query, String name, Context context) { SpannableString boldedInitials = new SpannableString(name); name = name.toLowerCase(); int initialsBolded = 0; @@ -91,7 +95,8 @@ public class QueryBoldingUtil { while (++nameIndex < name.length() && initialsBolded < query.length()) { if ((nameIndex == 0 || name.charAt(nameIndex - 1) == ' ') - && QueryFilteringUtil.getDigit(name.charAt(nameIndex)) == query.charAt(initialsBolded)) { + && QueryFilteringUtil.getDigit(name.charAt(nameIndex), context) + == query.charAt(initialsBolded)) { boldedInitials.setSpan( new StyleSpan(Typeface.BOLD), nameIndex, diff --git a/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java index 6b5cea88d..1ecb486d2 100644 --- a/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java +++ b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java @@ -16,14 +16,24 @@ package com.android.dialer.searchfragment.common; +import android.content.Context; import android.support.annotation.NonNull; +import android.support.v4.util.SimpleArrayMap; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; +import com.android.dialer.dialpadview.DialpadCharMappings; import java.util.regex.Pattern; /** Utility class for filtering, comparing and handling strings and queries. */ public class QueryFilteringUtil { + /** + * The default character-digit map that will be used to find the digit associated with a given + * character on a T9 keyboard. + */ + private static final SimpleArrayMap DEFAULT_CHAR_TO_DIGIT_MAP = + DialpadCharMappings.getDefaultCharToKeyMap(); + /** Matches strings with "-", "(", ")", 2-9 of at least length one. */ private static final Pattern T9_PATTERN = Pattern.compile("[\\-()2-9]+"); @@ -38,15 +48,29 @@ public class QueryFilteringUtil { *
  • #nameMatchesT9Query("56", "Jessica Jones") returns true, 56 -> 'Jo' *
  • #nameMatchesT9Query("7", "Jessica Jones") returns false, no names start with P,Q,R or S * + * + *

    When the 1st language preference uses a non-Latin alphabet (e.g., Russian) and the character + * mappings for the alphabet is defined in {@link DialpadCharMappings}, the Latin alphabet will be + * used first to check if the name matches the query. If they don't match, the non-Latin alphabet + * will be used. + * + *

    Examples (when the 1st language preference is Russian): + * + *

      + *
    • #nameMatchesT9Query("7", "John Smith") returns true, 7 -> 'S' + *
    • #nameMatchesT9Query("7", "Павел Чехов") returns true, 7 -> 'Ч' + *
    • #nameMatchesT9Query("77", "Pavel Чехов") returns true, 7 -> 'P' (in the Latin alphabet), + * 7 -> 'Ч' (in the Russian alphabet) + *
    */ - public static boolean nameMatchesT9Query(String query, String name) { + public static boolean nameMatchesT9Query(String query, String name, Context context) { 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()) { + if (pattern.matcher(getT9Representation(name, context)).find()) { // query matches the start of a T9 name (i.e. 75 -> "Jessica [Jo]nes") return true; } @@ -61,7 +85,7 @@ public class QueryFilteringUtil { continue; } - if (getDigit(names[i].charAt(0)) == query.charAt(queryIndex)) { + if (getDigit(names[i].charAt(0), context) == query.charAt(queryIndex)) { queryIndex++; } } @@ -106,11 +130,17 @@ public class QueryFilteringUtil { return digitsOnly(number).indexOf(digitsOnly(query)); } - // Returns string with letters replaced with their T9 representation. - static String getT9Representation(String s) { + /** + * Replaces characters in the given string with their T9 representations. + * + * @param s The original string + * @param context The context + * @return The original string with characters replaced with T9 representations. + */ + static String getT9Representation(String s, Context context) { StringBuilder builder = new StringBuilder(s.length()); for (char c : s.toLowerCase().toCharArray()) { - builder.append(getDigit(c)); + builder.append(getDigit(c, context)); } return builder.toString(); } @@ -127,45 +157,26 @@ public class QueryFilteringUtil { 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; + /** + * Returns the digit on a T9 keyboard which is associated with the given lower case character. + * + *

    The default character-key mapping will be used first to find a digit. If no digit is found, + * try the mapping of the current default locale if it is defined in {@link DialpadCharMappings}. + * If the second attempt fails, return the original character. + */ + static char getDigit(char c, Context context) { + Character digit = DEFAULT_CHAR_TO_DIGIT_MAP.get(c); + if (digit != null) { + return digit; + } + + SimpleArrayMap charToKeyMap = + DialpadCharMappings.getCharToKeyMap(context); + if (charToKeyMap != null) { + digit = charToKeyMap.get(c); + return digit != null ? digit : c; } + + return c; } } diff --git a/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java b/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java index 84c22a2cf..df67b762f 100644 --- a/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java +++ b/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java @@ -17,6 +17,7 @@ package com.android.dialer.searchfragment.cp2; import android.content.ContentResolver; +import android.content.Context; import android.database.CharArrayBuffer; import android.database.ContentObserver; import android.database.Cursor; @@ -44,7 +45,7 @@ import java.util.Set; * Wrapper for a cursor containing all on device contacts. * *

    This cursor removes duplicate phone numbers associated with the same contact and can filter - * contacts based on a query by calling {@link #filter(String)}. + * contacts based on a query by calling {@link #filter(String, Context)}. */ final class ContactFilterCursor implements Cursor { @@ -72,10 +73,11 @@ final class ContactFilterCursor implements Cursor { /** * @param cursor with projection {@link Projections#CP2_PROJECTION}. * @param query to filter cursor results. + * @param context of the app. */ - ContactFilterCursor(Cursor cursor, @Nullable String query) { + ContactFilterCursor(Cursor cursor, @Nullable String query, Context context) { this.cursor = createCursor(cursor); - filter(query); + filter(query, context); } /** @@ -238,7 +240,7 @@ final class ContactFilterCursor implements Cursor { *

  • Its company contains the query * */ - public void filter(@Nullable String query) { + public void filter(@Nullable String query, Context context) { if (query == null) { query = ""; } @@ -253,7 +255,7 @@ final class ContactFilterCursor implements Cursor { String companyName = cursor.getString(Projections.COMPANY_NAME); String nickName = cursor.getString(Projections.NICKNAME); if (TextUtils.isEmpty(query) - || QueryFilteringUtil.nameMatchesT9Query(query, name) + || QueryFilteringUtil.nameMatchesT9Query(query, name, context) || QueryFilteringUtil.numberMatchesNumberQuery(query, number) || QueryFilteringUtil.nameContainsQuery(query, name) || QueryFilteringUtil.nameContainsQuery(query, companyName) diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java index c09396c72..386ab3a6b 100644 --- a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java +++ b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java @@ -104,7 +104,7 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick : context.getString( com.android.contacts.common.R.string.call_subject_type_and_number, label, number); - nameOrNumberView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name)); + nameOrNumberView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name, context)); numberView.setText(QueryBoldingUtil.getNumberWithQueryBolded(query, secondaryInfo)); setCallToAction(cursor, query); diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursor.java b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursor.java index 508ca7f57..7697e0520 100644 --- a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursor.java +++ b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursor.java @@ -32,17 +32,19 @@ import com.android.dialer.searchfragment.common.SearchCursor; final class SearchContactsCursor extends MergeCursor implements SearchCursor { private final ContactFilterCursor contactFilterCursor; + private final Context context; static SearchContactsCursor newInstance( Context context, ContactFilterCursor contactFilterCursor) { MatrixCursor headerCursor = new MatrixCursor(HEADER_PROJECTION); headerCursor.addRow(new String[] {context.getString(R.string.all_contacts)}); - return new SearchContactsCursor(new Cursor[] {headerCursor, contactFilterCursor}); + return new SearchContactsCursor(new Cursor[] {headerCursor, contactFilterCursor}, context); } - private SearchContactsCursor(Cursor[] cursors) { + private SearchContactsCursor(Cursor[] cursors, Context context) { super(cursors); - contactFilterCursor = (ContactFilterCursor) cursors[1]; + this.contactFilterCursor = (ContactFilterCursor) cursors[1]; + this.context = context; } @Override @@ -52,7 +54,7 @@ final class SearchContactsCursor extends MergeCursor implements SearchCursor { @Override public boolean updateQuery(@Nullable String query) { - contactFilterCursor.filter(query); + contactFilterCursor.filter(query, context); return true; } diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java index d3abbffca..35518019e 100644 --- a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java +++ b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java @@ -61,7 +61,7 @@ public final class SearchContactsCursorLoader extends CursorLoader { // All contacts Cursor cursor = super.loadInBackground(); // Filtering logic - ContactFilterCursor contactFilterCursor = new ContactFilterCursor(cursor, query); + ContactFilterCursor contactFilterCursor = new ContactFilterCursor(cursor, query, getContext()); // Header logic return SearchContactsCursor.newInstance(getContext(), contactFilterCursor); } diff --git a/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java b/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java index 5d5188059..2e1fd5e9d 100644 --- a/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java +++ b/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java @@ -64,8 +64,8 @@ public final class NearbyPlaceViewHolder extends RecyclerView.ViewHolder String name = cursor.getString(Projections.DISPLAY_NAME); String address = cursor.getString(Projections.PHONE_LABEL); - placeName.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name)); - placeAddress.setText(QueryBoldingUtil.getNameWithQueryBolded(query, address)); + placeName.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name, context)); + placeAddress.setText(QueryBoldingUtil.getNameWithQueryBolded(query, address, context)); String photoUri = cursor.getString(Projections.PHOTO_URI); ContactPhotoManager.getInstance(context) .loadDialerThumbnailOrPhoto( diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java b/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java index 8a02eb9b9..339855fbb 100644 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java +++ b/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java @@ -72,8 +72,8 @@ public final class RemoteContactViewHolder extends RecyclerView.ViewHolder : context.getString( com.android.contacts.common.R.string.call_subject_type_and_number, label, number); - nameView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name)); - numberView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, secondaryInfo)); + nameView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name, context)); + numberView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, secondaryInfo, context)); if (shouldShowPhoto(cursor)) { nameView.setVisibility(View.VISIBLE); -- cgit v1.2.3 From 1fe02f5e572970e21b760d5774483bc859634982 Mon Sep 17 00:00:00 2001 From: linyuh Date: Fri, 3 Nov 2017 11:57:28 -0700 Subject: Add character mappings for Bulgarian & Ukrainian in DialpadCharMappings. Bug: 30215380 Test: Manual PiperOrigin-RevId: 174493347 Change-Id: I06d5d3d8877ac29d148b3945e28dc0a094660eca --- .../dialer/dialpadview/DialpadCharMappings.java | 49 +++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/java/com/android/dialer/dialpadview/DialpadCharMappings.java b/java/com/android/dialer/dialpadview/DialpadCharMappings.java index 12994bc78..03bc2e728 100644 --- a/java/com/android/dialer/dialpadview/DialpadCharMappings.java +++ b/java/com/android/dialer/dialpadview/DialpadCharMappings.java @@ -50,13 +50,34 @@ public class DialpadCharMappings { getCharToKeyMap(KEY_TO_CHARS); } + /** The character mapping for the Bulgarian alphabet */ + private static class Bul { + private static final String[] KEY_TO_CHARS = { + "" /* 0 */, + "" /* 1 */, + "АБВГ" /* 2 */, + "ДЕЖЗ" /* 3 */, + "ИЙКЛ" /* 4 */, + "МНО" /* 5 */, + "ПРС" /* 6 */, + "ТУФХ" /* 7 */, + "ЦЧШЩ" /* 8 */, + "ЪЬЮЯ" /* 9 */, + "" /* * */, + "" /* # */, + }; + + private static final SimpleArrayMap CHAR_TO_KEY = + getCharToKeyMap(KEY_TO_CHARS); + } + /** The character mapping for the Russian alphabet */ private static class Rus { private static final String[] KEY_TO_CHARS = { "" /* 0 */, "" /* 1 */, "АБВГ" /* 2 */, - "ДЕЖЗ" /* 3 */, + "ДЕЁЖЗ" /* 3 */, "ИЙКЛ" /* 4 */, "МНОП" /* 5 */, "РСТУ" /* 6 */, @@ -71,6 +92,27 @@ public class DialpadCharMappings { getCharToKeyMap(KEY_TO_CHARS); } + /** The character mapping for the Ukrainian alphabet */ + private static class Ukr { + private static final String[] KEY_TO_CHARS = { + "" /* 0 */, + "" /* 1 */, + "АБВГҐ" /* 2 */, + "ДЕЄЖЗ" /* 3 */, + "ИІЇЙКЛ" /* 4 */, + "МНОП" /* 5 */, + "РСТУ" /* 6 */, + "ФХЦЧ" /* 7 */, + "ШЩ" /* 8 */, + "ЬЮЯ" /* 9 */, + "" /* * */, + "" /* # */, + }; + + private static final SimpleArrayMap CHAR_TO_KEY = + getCharToKeyMap(KEY_TO_CHARS); + } + // A map in which each key is an ISO 639-2 language code and the corresponding value is a // character-key map. private static final SimpleArrayMap> @@ -81,8 +123,13 @@ public class DialpadCharMappings { private static final SimpleArrayMap KEY_TO_CHAR_MAPS = new SimpleArrayMap<>(); static { + CHAR_TO_KEY_MAPS.put("bul", Bul.CHAR_TO_KEY); CHAR_TO_KEY_MAPS.put("rus", Rus.CHAR_TO_KEY); + CHAR_TO_KEY_MAPS.put("ukr", Ukr.CHAR_TO_KEY); + + KEY_TO_CHAR_MAPS.put("bul", Bul.KEY_TO_CHARS); KEY_TO_CHAR_MAPS.put("rus", Rus.KEY_TO_CHARS); + KEY_TO_CHAR_MAPS.put("ukr", Ukr.KEY_TO_CHARS); } /** -- cgit v1.2.3 From 6af2e14a28e27a38525a08c920e7453c2448689a Mon Sep 17 00:00:00 2001 From: calderwoodra Date: Fri, 3 Nov 2017 15:53:04 -0700 Subject: Implement bulk update for Cp2PhoneLookup. Test: Cp2PhoneLookupTest PiperOrigin-RevId: 174525877 Change-Id: I7888f3b6adc58416c560271166ec6bd85306d58b --- .../dialer/phonelookup/cp2/Cp2PhoneLookup.java | 337 ++++++++++++++++++--- .../dialer/phonelookup/phone_lookup_info.proto | 17 +- 2 files changed, 318 insertions(+), 36 deletions(-) diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java index f9fc1a6f4..2878e27c4 100644 --- a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java @@ -22,23 +22,47 @@ import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.DeletedContacts; import android.support.annotation.NonNull; +import android.support.v4.util.ArrayMap; import android.support.v4.util.ArraySet; import android.telecom.Call; +import android.text.TextUtils; import com.android.dialer.DialerPhoneNumber; +import com.android.dialer.common.Assert; import com.android.dialer.common.concurrent.DialerExecutors; import com.android.dialer.inject.ApplicationContext; import com.android.dialer.phonelookup.PhoneLookup; import com.android.dialer.phonelookup.PhoneLookupInfo; +import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import javax.inject.Inject; /** PhoneLookup implementation for local contacts. */ public final class Cp2PhoneLookup implements PhoneLookup { + private static final String[] CP2_INFO_PROJECTION = + new String[] { + Phone.DISPLAY_NAME_PRIMARY, // 0 + Phone.PHOTO_THUMBNAIL_URI, // 1 + Phone.PHOTO_ID, // 2 + Phone.LABEL, // 3 + Phone.NORMALIZED_NUMBER, // 4 + Phone.CONTACT_ID, // 5 + }; + + private static final int CP2_INFO_NAME_INDEX = 0; + private static final int CP2_INFO_PHOTO_URI_INDEX = 1; + private static final int CP2_INFO_PHOTO_ID_INDEX = 2; + private static final int CP2_INFO_LABEL_INDEX = 3; + private static final int CP2_INFO_NUMBER_INDEX = 4; + private static final int CP2_INFO_CONTACT_ID_INDEX = 5; + private final Context appContext; @Inject @@ -60,12 +84,12 @@ public final class Cp2PhoneLookup implements PhoneLookup { } private boolean isDirtyInternal(ImmutableSet phoneNumbers, long lastModified) { - return contactsUpdated(getContactIdsFromPhoneNumbers(phoneNumbers), lastModified) + return contactsUpdated(queryPhoneTableForContactIds(phoneNumbers), lastModified) || contactsDeleted(lastModified); } /** Returns set of contact ids that correspond to {@code phoneNumbers} if the contact exists. */ - private Set getContactIdsFromPhoneNumbers(ImmutableSet phoneNumbers) { + private Set queryPhoneTableForContactIds(ImmutableSet phoneNumbers) { Set contactIds = new ArraySet<>(); try (Cursor cursor = appContext @@ -73,7 +97,7 @@ public final class Cp2PhoneLookup implements PhoneLookup { .query( Phone.CONTENT_URI, new String[] {Phone.CONTACT_ID}, - columnInSetWhereStatement(Phone.NORMALIZED_NUMBER, phoneNumbers.size()), + Phone.NORMALIZED_NUMBER + " IN (" + questionMarks(phoneNumbers.size()) + ")", contactIdsSelectionArgs(phoneNumbers), null)) { cursor.moveToPosition(-1); @@ -100,37 +124,32 @@ public final class Cp2PhoneLookup implements PhoneLookup { /** Returns true if any contacts were modified after {@code lastModified}. */ private boolean contactsUpdated(Set contactIds, long lastModified) { - try (Cursor cursor = - appContext - .getContentResolver() - .query( - Contacts.CONTENT_URI, - new String[] {Contacts._ID}, - contactsIsDirtyWhereStatement(contactIds.size()), - contactsIsDirtySelectionArgs(lastModified, contactIds), - null)) { + try (Cursor cursor = queryContactsTableForContacts(contactIds, lastModified)) { return cursor.getCount() > 0; } } - private static String contactsIsDirtyWhereStatement(int numberOfContactIds) { - StringBuilder where = new StringBuilder(); - // Filter to after last modified time - where.append(Contacts.CONTACT_LAST_UPDATED_TIMESTAMP).append(" > ?"); - - // Filter based only on contacts we care about - where.append(" AND ").append(columnInSetWhereStatement(Contacts._ID, numberOfContactIds)); - return where.toString(); - } + private Cursor queryContactsTableForContacts(Set contactIds, long lastModified) { + // Filter to after last modified time based only on contacts we care about + String where = + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + + " > ?" + + " AND " + + Contacts._ID + + " IN (" + + questionMarks(contactIds.size()) + + ")"; - private String[] contactsIsDirtySelectionArgs(long lastModified, Set contactIds) { String[] args = new String[contactIds.size() + 1]; args[0] = Long.toString(lastModified); int i = 1; for (Long contactId : contactIds) { args[i++] = Long.toString(contactId); } - return args; + + return appContext + .getContentResolver() + .query(Contacts.CONTENT_URI, new String[] {Contacts._ID}, where, args, null); } /** Returns true if any contacts were deleted after {@code lastModified}. */ @@ -148,22 +167,272 @@ public final class Cp2PhoneLookup implements PhoneLookup { } } - private static String columnInSetWhereStatement(String columnName, int setSize) { + @Override + public ListenableFuture> bulkUpdate( + ImmutableMap existingInfoMap, long lastModified) { + return MoreExecutors.listeningDecorator(DialerExecutors.getLowPriorityThreadPool(appContext)) + .submit(() -> bulkUpdateInternal(existingInfoMap, lastModified)); + } + + private ImmutableMap bulkUpdateInternal( + ImmutableMap existingInfoMap, long lastModified) { + // Build a set of each DialerPhoneNumber that was associated with a contact, and is no longer + // associated with that same contact. + Set deletedPhoneNumbers = + getDeletedPhoneNumbers(existingInfoMap, lastModified); + + // For each DialerPhoneNumber that was associated with a contact or added to a contact, + // build a map of those DialerPhoneNumbers to a set Cp2Infos, where each Cp2Info represents a + // contact. + ImmutableMap> updatedContacts = + buildMapForUpdatedOrAddedContacts(existingInfoMap, lastModified, deletedPhoneNumbers); + + // Start build a new map of updated info. This will replace existing info. + ImmutableMap.Builder newInfoMapBuilder = + ImmutableMap.builder(); + + // For each DialerPhoneNumber in existing info... + for (Entry entry : existingInfoMap.entrySet()) { + // Build off the existing info + PhoneLookupInfo.Builder infoBuilder = PhoneLookupInfo.newBuilder(entry.getValue()); + + // If the contact was updated, replace the Cp2Info list + if (updatedContacts.containsKey(entry.getKey())) { + infoBuilder.clearCp2Info(); + infoBuilder.addAllCp2Info(updatedContacts.get(entry.getKey())); + + // If it was deleted and not added to a new contact, replace the Cp2Info list with + // the default instance of Cp2Info + } else if (deletedPhoneNumbers.contains(entry.getKey())) { + infoBuilder.clearCp2Info(); + infoBuilder.addCp2Info(Cp2Info.getDefaultInstance()); + } + + // If the DialerPhoneNumber didn't change, add the unchanged existing info. + newInfoMapBuilder.put(entry.getKey(), infoBuilder.build()); + } + return newInfoMapBuilder.build(); + } + + /** + * 1. get all contact ids. if the id is unset, add the number to the list of contacts to look up. + * 2. reduce our list of contact ids to those that were updated after lastModified. 3. Now we have + * the smallest set of dialer phone numbers to query cp2 against. 4. build and return the map of + * dialerphonenumbers to their new cp2info + * + * @return Map of {@link DialerPhoneNumber} to {@link PhoneLookupInfo} with updated {@link + * Cp2Info}. + */ + private ImmutableMap> buildMapForUpdatedOrAddedContacts( + ImmutableMap existingInfoMap, + long lastModified, + Set deletedPhoneNumbers) { + + // Start building a set of DialerPhoneNumbers that we want to update. + Set updatedNumbers = new ArraySet<>(); + + Set contactIds = new ArraySet<>(); + for (Entry entry : existingInfoMap.entrySet()) { + // If the number was deleted, we need to check if it was added to a new contact. + if (deletedPhoneNumbers.contains(entry.getKey())) { + updatedNumbers.add(entry.getKey()); + continue; + } + + // For each Cp2Info for each existing DialerPhoneNumber... + // Store the contact id if it exist, else automatically add the DialerPhoneNumber to our + // set of DialerPhoneNumbers we want to update. + for (Cp2Info cp2Info : entry.getValue().getCp2InfoList()) { + if (Objects.equals(cp2Info, Cp2Info.getDefaultInstance())) { + // If the number doesn't have any Cp2Info set to it, for various reasons, we need to look + // up the number to check if any exists. + // The various reasons this might happen are: + // - An existing contact that wasn't in the call log is now in the call log. + // - A number was in the call log before but has now been added to a contact. + // - A number is in the call log, but isn't associated with any contact. + updatedNumbers.add(entry.getKey()); + } else { + contactIds.add(cp2Info.getContactId()); + } + } + } + + // Query the contacts table and get those that whose Contacts.CONTACT_LAST_UPDATED_TIMESTAMP is + // after lastModified, such that Contacts._ID is in our set of contact IDs we build above. + try (Cursor cursor = queryContactsTableForContacts(contactIds, lastModified)) { + int contactIdIndex = cursor.getColumnIndex(Contacts._ID); + cursor.moveToPosition(-1); + while (cursor.moveToNext()) { + // Find the DialerPhoneNumber for each contact id and add it to our updated numbers set. + // These, along with our number not associated with any Cp2Info need to be updated. + long contactId = cursor.getLong(contactIdIndex); + updatedNumbers.addAll(getDialerPhoneNumber(existingInfoMap, contactId)); + } + } + + // Query the Phone table and build Cp2Info for each DialerPhoneNumber in our updatedNumbers set. + Map> map = new ArrayMap<>(); + try (Cursor cursor = getAllCp2Rows(updatedNumbers)) { + cursor.moveToPosition(-1); + while (cursor.moveToNext()) { + // Map each dialer phone number to it's new cp2 info + Set phoneNumbers = + getDialerPhoneNumbers(updatedNumbers, cursor.getString(CP2_INFO_NUMBER_INDEX)); + Cp2Info info = buildCp2InfoFromUpdatedContactsCursor(cursor); + for (DialerPhoneNumber phoneNumber : phoneNumbers) { + if (map.containsKey(phoneNumber)) { + map.get(phoneNumber).add(info); + } else { + Set cp2Infos = new ArraySet<>(); + cp2Infos.add(info); + map.put(phoneNumber, cp2Infos); + } + } + } + } + return ImmutableMap.copyOf(map); + } + + /** + * Returns cursor with projection {@link #CP2_INFO_PROJECTION} and only phone numbers that are in + * {@code updateNumbers}. + */ + private Cursor getAllCp2Rows(Set updatedNumbers) { + String where = Phone.NORMALIZED_NUMBER + " IN (" + questionMarks(updatedNumbers.size()) + ")"; + String[] selectionArgs = new String[updatedNumbers.size()]; + int i = 0; + for (DialerPhoneNumber phoneNumber : updatedNumbers) { + selectionArgs[i++] = getNormalizedNumber(phoneNumber); + } + + return appContext + .getContentResolver() + .query(Phone.CONTENT_URI, CP2_INFO_PROJECTION, where, selectionArgs, null); + } + + /** + * @param cursor with projection {@link #CP2_INFO_PROJECTION}. + * @return new {@link Cp2Info} based on current row of {@code cursor}. + */ + private static Cp2Info buildCp2InfoFromUpdatedContactsCursor(Cursor cursor) { + String displayName = cursor.getString(CP2_INFO_NAME_INDEX); + String photoUri = cursor.getString(CP2_INFO_PHOTO_URI_INDEX); + String label = cursor.getString(CP2_INFO_LABEL_INDEX); + + Cp2Info.Builder infoBuilder = Cp2Info.newBuilder(); + if (!TextUtils.isEmpty(displayName)) { + infoBuilder.setName(displayName); + } + if (!TextUtils.isEmpty(photoUri)) { + infoBuilder.setPhotoUri(photoUri); + } + if (!TextUtils.isEmpty(label)) { + infoBuilder.setLabel(label); + } + infoBuilder.setPhotoId(cursor.getLong(CP2_INFO_PHOTO_ID_INDEX)); + infoBuilder.setContactId(cursor.getLong(CP2_INFO_CONTACT_ID_INDEX)); + return infoBuilder.build(); + } + + /** Returns set of DialerPhoneNumbers that were associated with now deleted contacts. */ + private Set getDeletedPhoneNumbers( + ImmutableMap existingInfoMap, long lastModified) { + // Build set of all contact IDs from our existing data. We're going to use this set to query + // against the DeletedContacts table and see if any of them were deleted. + Set contactIds = findContactIdsIn(existingInfoMap); + + // Start building a set of DialerPhoneNumbers that were associated with now deleted contacts. + try (Cursor cursor = queryDeletedContacts(contactIds, lastModified)) { + // We now have a cursor/list of contact IDs that were associated with deleted contacts. + return findDeletedPhoneNumbersIn(existingInfoMap, cursor); + } + } + + private Set findContactIdsIn(ImmutableMap map) { + Set contactIds = new ArraySet<>(); + for (PhoneLookupInfo info : map.values()) { + for (Cp2Info cp2Info : info.getCp2InfoList()) { + contactIds.add(cp2Info.getContactId()); + } + } + return contactIds; + } + + private Cursor queryDeletedContacts(Set contactIds, long lastModified) { + String where = + DeletedContacts.CONTACT_DELETED_TIMESTAMP + + " > ?" + + " AND " + + DeletedContacts.CONTACT_ID + + " IN (" + + questionMarks(contactIds.size()) + + ")"; + String[] args = new String[contactIds.size() + 1]; + args[0] = Long.toString(lastModified); + int i = 1; + for (Long contactId : contactIds) { + args[i++] = Long.toString(contactId); + } + + return appContext + .getContentResolver() + .query( + DeletedContacts.CONTENT_URI, + new String[] {DeletedContacts.CONTACT_ID}, + where, + args, + null); + } + + /** Returns set of DialerPhoneNumbers that are associated with deleted contact IDs. */ + private Set findDeletedPhoneNumbersIn( + ImmutableMap existingInfoMap, Cursor cursor) { + int contactIdIndex = cursor.getColumnIndexOrThrow(DeletedContacts.CONTACT_ID); + Set deletedPhoneNumbers = new ArraySet<>(); + cursor.moveToPosition(-1); + while (cursor.moveToNext()) { + long contactId = cursor.getLong(contactIdIndex); + deletedPhoneNumbers.addAll(getDialerPhoneNumber(existingInfoMap, contactId)); + } + return deletedPhoneNumbers; + } + + private static Set getDialerPhoneNumbers( + Set phoneNumbers, String number) { + Set matches = new ArraySet<>(); + for (DialerPhoneNumber phoneNumber : phoneNumbers) { + if (getNormalizedNumber(phoneNumber).equals(number)) { + matches.add(phoneNumber); + } + } + Assert.checkArgument( + matches.size() > 0, "Couldn't find DialerPhoneNumber for number: " + number); + return matches; + } + + private static Set getDialerPhoneNumber( + ImmutableMap existingInfoMap, long contactId) { + Set matches = new ArraySet<>(); + for (Entry entry : existingInfoMap.entrySet()) { + for (Cp2Info cp2Info : entry.getValue().getCp2InfoList()) { + if (cp2Info.getContactId() == contactId) { + matches.add(entry.getKey()); + } + } + } + Assert.checkArgument( + matches.size() > 0, "Couldn't find DialerPhoneNumber for contact ID: " + contactId); + return matches; + } + + private static String questionMarks(int count) { StringBuilder where = new StringBuilder(); - where.append(columnName).append(" IN ("); - for (int i = 0; i < setSize; i++) { + for (int i = 0; i < count; i++) { if (i != 0) { where.append(", "); } where.append("?"); } - return where.append(")").toString(); - } - - @Override - public ListenableFuture> bulkUpdate( - ImmutableMap existingInfoMap, long lastModified) { - // TODO(calderwoodra) - return null; + return where.toString(); } } diff --git a/java/com/android/dialer/phonelookup/phone_lookup_info.proto b/java/com/android/dialer/phonelookup/phone_lookup_info.proto index 1027e5c22..cb89a64e3 100644 --- a/java/com/android/dialer/phonelookup/phone_lookup_info.proto +++ b/java/com/android/dialer/phonelookup/phone_lookup_info.proto @@ -17,10 +17,23 @@ message PhoneLookupInfo { // Information about a PhoneNumber retrieved from CP2. Cp2PhoneLookup is // responsible for populating the data in this message. message Cp2Info { + // android.provider.ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME_PRIMARY optional string name = 1; + + // android.provider.ContactsContract.CommonDataKinds.Phone.PHOTO_THUMBNAIL_URI optional string photo_uri = 2; + + // android.provider.ContactsContract.CommonDataKinds.Phone.PHOTO_ID optional fixed64 photo_id = 3; - optional string label = 4; // "Home", "Mobile", ect. + + // android.provider.ContactsContract.CommonDataKinds.Phone.LABEL + // "Home", "Mobile", ect. + optional string label = 4; + + // android.provider.ContactsContract.CommonDataKinds.Phone.CONTACT_ID + optional fixed64 contact_id = 5; } - optional Cp2Info cp2_info = 1; + // Repeated because one phone number can be associated with multiple CP2 + // contacts. + repeated Cp2Info cp2_info = 1; } \ No newline at end of file -- cgit v1.2.3 From 1a56facd7bf477e6fb8f0ec1093696ba83e4fd74 Mon Sep 17 00:00:00 2001 From: mdooley Date: Fri, 3 Nov 2017 16:08:08 -0700 Subject: Allow the client to specify voicemail transcript id's This is needed for rating transcription quality since we don't persist the transcript id. The id we generate is just the md5 hash of the voicemail audio data. Bug: 68712148 Test: manual and updated unit tests PiperOrigin-RevId: 174527907 Change-Id: I7a29bf5a96447129fc1437074f20ecebcdb1f43b --- .../transcribe/TranscriptionConfigProvider.java | 5 +++ .../impl/transcribe/TranscriptionTaskAsync.java | 21 +++++++---- .../impl/transcribe/TranscriptionUtils.java | 41 ++++++++++++++++++++++ 3 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java b/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java index f4996a097..98c8461f5 100644 --- a/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java +++ b/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java @@ -80,6 +80,11 @@ public class TranscriptionConfigProvider { .getBoolean("voicemail_transcription_donation_available", false); } + public boolean useClientGeneratedVoicemailIds() { + return ConfigProviderBindings.get(context) + .getBoolean("voicemail_transcription_client_generated_voicemail_ids", false); + } + @Override public String toString() { return String.format( diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java index f946607b5..808bf0f87 100644 --- a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java +++ b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java @@ -17,6 +17,7 @@ package com.android.voicemail.impl.transcribe; import android.app.job.JobWorkItem; import android.content.Context; +import android.support.annotation.VisibleForTesting; import android.util.Pair; import com.android.dialer.common.Assert; import com.android.dialer.logging.DialerImpression; @@ -121,13 +122,21 @@ public class TranscriptionTaskAsync extends TranscriptionTask { return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY); } + @VisibleForTesting TranscribeVoicemailAsyncRequest getUploadRequest() { - return TranscribeVoicemailAsyncRequest.newBuilder() - .setVoicemailData(audioData) - .setAudioFormat(encoding) - .setDonationPreference( - isDonationEnabled() ? DonationPreference.DONATE : DonationPreference.DO_NOT_DONATE) - .build(); + TranscribeVoicemailAsyncRequest.Builder builder = + TranscribeVoicemailAsyncRequest.newBuilder() + .setVoicemailData(audioData) + .setAudioFormat(encoding) + .setDonationPreference( + isDonationEnabled() ? DonationPreference.DONATE : DonationPreference.DO_NOT_DONATE); + // Generate the transcript id locally if configured to do so, or if voicemail donation is + // available (because rating donating voicemails requires locally generated voicemail ids). + if (configProvider.useClientGeneratedVoicemailIds() + || configProvider.isVoicemailDonationAvailable()) { + builder.setTranscriptionId(TranscriptionUtils.getFingerprintFor(audioData)); + } + return builder.build(); } private boolean isDonationEnabled() { diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java b/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java new file mode 100644 index 000000000..a001f179a --- /dev/null +++ b/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java @@ -0,0 +1,41 @@ +/* + * 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.voicemail.impl.transcribe; + +import android.annotation.TargetApi; +import android.os.Build.VERSION_CODES; +import android.util.Base64; +import com.android.dialer.common.Assert; +import com.google.protobuf.ByteString; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** Utility methods used by this transcription package. */ +public class TranscriptionUtils { + + @TargetApi(VERSION_CODES.O) + static String getFingerprintFor(ByteString data) { + Assert.checkArgument(data != null); + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] md5Bytes = md.digest(data.toByteArray()); + return Base64.encodeToString(md5Bytes, Base64.DEFAULT); + } catch (NoSuchAlgorithmException e) { + Assert.fail(e.toString()); + } + return null; + } +} -- cgit v1.2.3 From 60dd96aa640ed4619de915576b5ed0df53081fc4 Mon Sep 17 00:00:00 2001 From: zachh Date: Fri, 3 Nov 2017 16:45:34 -0700 Subject: Implemented CompositePhoneLookup#bulkUpdate. Bug: 34672501 Test: unit PiperOrigin-RevId: 174532642 Change-Id: I0115fb26f99fe764bc90625e3ed51f3c4c99439d --- .../composite/CompositePhoneLookup.java | 41 +++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java index ba08fe9bf..f85b357e7 100644 --- a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java +++ b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java @@ -19,6 +19,7 @@ package com.android.dialer.phonelookup.composite; import android.support.annotation.NonNull; import android.telecom.Call; import com.android.dialer.DialerPhoneNumber; +import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerFutures; import com.android.dialer.phonelookup.PhoneLookup; import com.android.dialer.phonelookup.PhoneLookupInfo; @@ -85,9 +86,47 @@ public final class CompositePhoneLookup implements PhoneLookup { futures, Preconditions::checkNotNull, false /* defaultValue */); } + /** + * Delegates to a set of dependent lookups and combines results. + * + *

    Note: If any of the dependent lookups fails, the returned future will also fail. If any of + * the dependent lookups does not complete, the returned future will also not complete. + */ @Override public ListenableFuture> bulkUpdate( ImmutableMap existingInfoMap, long lastModified) { - return null; + List>> futures = + new ArrayList<>(); + for (PhoneLookup phoneLookup : phoneLookups) { + futures.add(phoneLookup.bulkUpdate(existingInfoMap, lastModified)); + } + return Futures.transform( + Futures.allAsList(futures), + new Function< + List>, + ImmutableMap>() { + @Override + public ImmutableMap apply( + List> allMaps) { + ImmutableMap.Builder combinedMap = + ImmutableMap.builder(); + for (DialerPhoneNumber dialerPhoneNumber : existingInfoMap.keySet()) { + PhoneLookupInfo.Builder combinedInfo = PhoneLookupInfo.newBuilder(); + for (ImmutableMap map : allMaps) { + PhoneLookupInfo subInfo = map.get(dialerPhoneNumber); + if (subInfo == null) { + throw new IllegalStateException( + "A sublookup didn't return an info for number: " + + LogUtil.sanitizePhoneNumber( + dialerPhoneNumber.getRawInput().getNumber())); + } + combinedInfo.mergeFrom(subInfo); + } + combinedMap.put(dialerPhoneNumber, combinedInfo.build()); + } + return combinedMap.build(); + } + }, + MoreExecutors.directExecutor()); } } -- cgit v1.2.3