From 8d272bb15d3bb20b541da9f675bc5738cdfc2484 Mon Sep 17 00:00:00 2001 From: calderwoodra Date: Mon, 20 Nov 2017 10:40:49 -0800 Subject: Search now supports alternative sort and display orders for contact names. This means that names like "Bob Dylan" can be displayed as "Dylan, Bob" and the names are now able to be sorted by last name as well. This change also adds content descriptions to search icon for a11y. Bug: 68293751 Test: NewSearchFragmentTest PiperOrigin-RevId: 176382228 Change-Id: I5b2b0f3f3f1e2f23ea6b745fd809adfb0ba6242c --- .../dialer/searchfragment/common/Projections.java | 31 +++++++++++++++++++--- .../searchfragment/cp2/ContactFilterCursor.java | 20 +++++++++----- .../cp2/SearchContactViewHolder.java | 4 +++ .../cp2/SearchContactsCursorLoader.java | 9 +++++++ 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/java/com/android/dialer/searchfragment/common/Projections.java b/java/com/android/dialer/searchfragment/common/Projections.java index 63fac4ca0..cebe5c9a9 100644 --- a/java/com/android/dialer/searchfragment/common/Projections.java +++ b/java/com/android/dialer/searchfragment/common/Projections.java @@ -39,8 +39,10 @@ public class Projections { @SuppressWarnings("unused") public static final int SORT_KEY = 11; - public static final int COMPANY_NAME = 12; - public static final int NICKNAME = 13; + public static final int SORT_ALTERNATIVE = 12; + + public static final int COMPANY_NAME = 13; + public static final int NICKNAME = 14; public static final String[] CP2_PROJECTION = new String[] { @@ -56,8 +58,29 @@ public class Projections { Data.CONTACT_ID, // 9 Data.MIMETYPE, // 10 Data.SORT_KEY_PRIMARY, // 11 - Organization.COMPANY, // 12 - Nickname.NAME // 13 + Data.SORT_KEY_ALTERNATIVE, // 12 + Organization.COMPANY, // 13 + Nickname.NAME // 14 + }; + + // Uses alternative display names (i.e. "Bob Dylan" becomes "Dylan, Bob"). + public static final String[] CP2_PROJECTION_ALTERNATIVE = + new String[] { + Data._ID, // 0 + Phone.TYPE, // 1 + Phone.LABEL, // 2 + Phone.NUMBER, // 3 + Data.DISPLAY_NAME_ALTERNATIVE, // 4 + Data.PHOTO_ID, // 5 + Data.PHOTO_THUMBNAIL_URI, // 6 + Data.LOOKUP_KEY, // 7 + Data.CARRIER_PRESENCE, // 8 + Data.CONTACT_ID, // 9 + Data.MIMETYPE, // 10 + Data.SORT_KEY_PRIMARY, // 11 + Data.SORT_KEY_ALTERNATIVE, // 12 + Organization.COMPANY, // 13 + Nickname.NAME // 14 }; public static final String[] DATA_PROJECTION = diff --git a/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java b/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java index dc16f9dd0..23c0d6e6d 100644 --- a/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java +++ b/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java @@ -32,6 +32,7 @@ import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.support.v4.util.ArraySet; import android.text.TextUtils; +import android.util.ArrayMap; import com.android.dialer.searchfragment.common.Projections; import com.android.dialer.searchfragment.common.QueryFilteringUtil; import java.lang.annotation.Retention; @@ -40,6 +41,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; /** @@ -108,25 +110,24 @@ final class ContactFilterCursor implements Cursor { private static Cursor createCursor(Cursor cursor) { // Convert cursor rows into Cp2Contacts List cp2Contacts = new ArrayList<>(); - Set contactIds = new ArraySet<>(); + Map contactIdsToPosition = new ArrayMap<>(); cursor.moveToPosition(-1); while (cursor.moveToNext()) { Cp2Contact contact = Cp2Contact.fromCursor(cursor); cp2Contacts.add(contact); - contactIds.add(contact.contactId()); + contactIdsToPosition.put(contact.contactId(), cursor.getPosition()); } cursor.close(); // Group then combine contact data List coalescedContacts = new ArrayList<>(); - for (Integer contactId : contactIds) { + for (Integer contactId : contactIdsToPosition.keySet()) { List duplicateContacts = getAllContactsWithContactId(contactId, cp2Contacts); coalescedContacts.addAll(coalesceContacts(duplicateContacts)); } - // Sort by display name, then build new cursor from coalesced contacts. - // We sort the contacts so that they are displayed to the user in lexicographic order. - Collections.sort(coalescedContacts, (o1, o2) -> o1.displayName().compareTo(o2.displayName())); + // Sort the contacts back into the exact same order they were inside of {@code cursor} + Collections.sort(coalescedContacts, (o1, o2) -> compare(contactIdsToPosition, o1, o2)); MatrixCursor newCursor = new MatrixCursor(Projections.CP2_PROJECTION, coalescedContacts.size()); for (Cp2Contact contact : coalescedContacts) { newCursor.addRow(contact.toCursorRow()); @@ -166,6 +167,13 @@ final class ContactFilterCursor implements Cursor { return coalescedContacts; } + private static int compare( + Map contactIdsToPosition, Cp2Contact o1, Cp2Contact o2) { + int position1 = contactIdsToPosition.get(o1.contactId()); + int position2 = contactIdsToPosition.get(o2.contactId()); + return Integer.compare(position1, position2); + } + private static void removeDuplicatePhoneNumbers(List phoneContacts) { for (int i = 0; i < phoneContacts.size(); i++) { Cp2Contact contact1 = phoneContacts.get(i); diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java index 386ab3a6b..e36df4bf7 100644 --- a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java +++ b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java @@ -171,6 +171,8 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick callToActionView.setVisibility(View.VISIBLE); callToActionView.setImageDrawable( context.getDrawable(com.android.contacts.common.R.drawable.ic_phone_attach)); + callToActionView.setContentDescription( + context.getString(R.string.description_search_call_and_share)); callToActionView.setOnClickListener(this); break; case CallToAction.DUO_CALL: @@ -178,6 +180,8 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick callToActionView.setVisibility(View.VISIBLE); callToActionView.setImageDrawable( context.getDrawable(R.drawable.quantum_ic_videocam_white_24)); + callToActionView.setContentDescription( + context.getString(R.string.description_search_video_call)); callToActionView.setOnClickListener(this); break; default: diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java index 2b7af1131..7624bc712 100644 --- a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java +++ b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java @@ -24,6 +24,7 @@ import android.provider.ContactsContract.CommonDataKinds.Organization; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Data; import android.support.annotation.Nullable; +import com.android.contacts.common.preference.ContactsPreferences; import com.android.dialer.searchfragment.common.Projections; /** Cursor Loader for CP2 contacts. */ @@ -41,6 +42,14 @@ public final class SearchContactsCursorLoader extends CursorLoader { null, Phone.SORT_KEY_PRIMARY + " ASC"); this.query = query; + + ContactsPreferences preferences = new ContactsPreferences(getContext()); + if (preferences.getSortOrder() == ContactsPreferences.SORT_ORDER_ALTERNATIVE) { + setSortOrder(Phone.SORT_KEY_ALTERNATIVE + " ASC"); + } + if (preferences.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_ALTERNATIVE) { + setProjection(Projections.CP2_PROJECTION_ALTERNATIVE); + } } /** -- cgit v1.2.3 From 241be987e90cae4f83215bcdfe0594c8cba23be4 Mon Sep 17 00:00:00 2001 From: erfanian Date: Mon, 20 Nov 2017 10:41:31 -0800 Subject: Use correct field type for annotation definition. Test: TAP PiperOrigin-RevId: 176382315 Change-Id: I5bb2342012e401dd28dcce440126e73b744523e5 --- java/com/android/contacts/common/ContactsUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/com/android/contacts/common/ContactsUtils.java b/java/com/android/contacts/common/ContactsUtils.java index 66ccc90e7..bc0e42300 100644 --- a/java/com/android/contacts/common/ContactsUtils.java +++ b/java/com/android/contacts/common/ContactsUtils.java @@ -79,6 +79,7 @@ public class ContactsUtils { * running inside Work Profile. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({USER_TYPE_CURRENT, USER_TYPE_WORK}) + // TODO: Switch to @LongDef when @LongDef is available in the support library + @IntDef({(int) USER_TYPE_CURRENT, (int) USER_TYPE_WORK}) public @interface UserType {} } -- cgit v1.2.3 From f75954b1425038e13dabbf342613510930b48836 Mon Sep 17 00:00:00 2001 From: wangqi Date: Mon, 20 Nov 2017 11:09:02 -0800 Subject: Add more Cequint provider and signature check. This change expands Cequint Caller ID to more provider name and adds signature check to avoid fake provider fraud. Bug: 69061848,69310040 Test: CequintPackageUtilsTest PiperOrigin-RevId: 176386836 Change-Id: Ie8759ae5acb18fe8fe61f365065ae020428a7880 --- .../android/dialer/oem/CequintCallerIdManager.java | 39 +-- .../android/dialer/oem/CequintPackageUtils.java | 286 +++++++++++++++++++++ .../dialer/oem/res/values/motorola_config.xml | 6 + 3 files changed, 314 insertions(+), 17 deletions(-) create mode 100644 java/com/android/dialer/oem/CequintPackageUtils.java diff --git a/java/com/android/dialer/oem/CequintCallerIdManager.java b/java/com/android/dialer/oem/CequintCallerIdManager.java index df624e06a..48a5985ce 100644 --- a/java/com/android/dialer/oem/CequintCallerIdManager.java +++ b/java/com/android/dialer/oem/CequintCallerIdManager.java @@ -46,17 +46,10 @@ public class CequintCallerIdManager { private static final String CONFIG_CALLER_ID_ENABLED = "config_caller_id_enabled"; - private static final String PROVIDER_NAME = "com.cequint.ecid"; - - private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/lookup"); - private static final int CALLER_ID_LOOKUP_USER_PROVIDED_CID = 0x0001; private static final int CALLER_ID_LOOKUP_SYSTEM_PROVIDED_CID = 0x0002; private static final int CALLER_ID_LOOKUP_INCOMING_CALL = 0x0020; - private static final Uri CONTENT_URI_FOR_INCALL = - Uri.parse("content://" + PROVIDER_NAME + "/incalllookup"); - private static final String[] EMPTY_PROJECTION = new String[] {}; // Column names in Cequint provider. @@ -72,7 +65,7 @@ public class CequintCallerIdManager { private static final String DISPLAY_NAME = "cid_pDisplayName"; private static boolean hasAlreadyCheckedCequintCallerIdPackage; - private static boolean isCequintCallerIdEnabled; + private static String cequintProviderAuthority; // TODO(wangqi): Revisit it and maybe remove it if it's not necessary. private final ConcurrentHashMap callLogCache; @@ -98,16 +91,20 @@ public class CequintCallerIdManager { } if (!hasAlreadyCheckedCequintCallerIdPackage) { hasAlreadyCheckedCequintCallerIdPackage = true; - isCequintCallerIdEnabled = false; - try { - context.getPackageManager().getPackageInfo(PROVIDER_NAME, 0); - isCequintCallerIdEnabled = true; - } catch (PackageManager.NameNotFoundException e) { - isCequintCallerIdEnabled = false; + String[] providerNames = context.getResources().getStringArray(R.array.cequint_providers); + PackageManager packageManager = context.getPackageManager(); + for (String provider : providerNames) { + if (CequintPackageUtils.isCallerIdInstalled(packageManager, provider)) { + cequintProviderAuthority = provider; + LogUtil.i( + "CequintCallerIdManager.isCequintCallerIdEnabled", "found provider: %s", provider); + return true; + } } + LogUtil.d("CequintCallerIdManager.isCequintCallerIdEnabled", "no provider found"); } - return isCequintCallerIdEnabled; + return cequintProviderAuthority != null; } public static CequintCallerIdManager createInstanceForCallLog() { @@ -133,7 +130,7 @@ public class CequintCallerIdManager { flag |= CALLER_ID_LOOKUP_USER_PROVIDED_CID; } String[] flags = {cnapName, String.valueOf(flag)}; - return lookup(context, CONTENT_URI_FOR_INCALL, number, flags); + return lookup(context, getIncallLookupUri(), number, flags); } @WorkerThread @@ -150,7 +147,7 @@ public class CequintCallerIdManager { CequintCallerIdContact cequintCallerIdContact = lookup( context, - CONTENT_URI, + getLookupUri(), PhoneNumberUtils.stripSeparators(number), new String[] {"system"}); if (cequintCallerIdContact != null) { @@ -267,6 +264,14 @@ public class CequintCallerIdManager { return geoDescription; } + private static Uri getLookupUri() { + return Uri.parse("content://" + cequintProviderAuthority + "/lookup"); + } + + private static Uri getIncallLookupUri() { + return Uri.parse("content://" + cequintProviderAuthority + "/incalllookup"); + } + private CequintCallerIdManager() { callLogCache = new ConcurrentHashMap<>(); } diff --git a/java/com/android/dialer/oem/CequintPackageUtils.java b/java/com/android/dialer/oem/CequintPackageUtils.java new file mode 100644 index 000000000..66c900e8b --- /dev/null +++ b/java/com/android/dialer/oem/CequintPackageUtils.java @@ -0,0 +1,286 @@ +/* + * 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.oem; + +import android.annotation.SuppressLint; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.Signature; +import android.support.annotation.Nullable; +import com.android.dialer.common.LogUtil; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** Utility class to verify Cequint package information. */ +final class CequintPackageUtils { + + private static final int SIGNED_1024 = 0; + private static final int SIGNED_2048 = 1; + private static final int SIGNED_VZW = 2; + private static final int SIGNED_SPRINT = 3; + + // Known Caller Name ID fingerprints + private static final List callerIdFingerprints = new ArrayList<>(); + + static { + // 1024 signed + callerIdFingerprints.add( + SIGNED_1024, + new byte[] { + 0x1A, + 0x0C, + (byte) 0xF8, + (byte) 0x8D, + 0x5B, + (byte) 0xE2, + 0x6A, + (byte) 0xED, + 0x50, + (byte) 0x85, + (byte) 0xFE, + (byte) 0x88, + (byte) 0xA0, + (byte) 0x9E, + (byte) 0xEC, + 0x25, + 0x1E, + (byte) 0xCA, + 0x16, + (byte) 0x97, + 0x50, + (byte) 0xDA, + 0x21, + (byte) 0xCC, + 0x18, + (byte) 0xC9, + (byte) 0x98, + (byte) 0xAF, + 0x26, + (byte) 0xCD, + 0x06, + 0x71 + }); + // 2048 signed + callerIdFingerprints.add( + SIGNED_2048, + new byte[] { + (byte) 0xCA, + 0x2F, + (byte) 0xAE, + (byte) 0xF4, + 0x09, + (byte) 0xEF, + 0x4C, + 0x79, + (byte) 0xF8, + 0x4C, + (byte) 0xD8, + (byte) 0x97, + (byte) 0xBF, + 0x1A, + 0x15, + 0x0F, + (byte) 0xF0, + 0x5E, + 0x54, + 0x74, + (byte) 0xB6, + 0x4A, + (byte) 0xCA, + (byte) 0xCD, + 0x05, + 0x7E, + 0x1E, + (byte) 0x98, + (byte) 0xC6, + 0x1F, + 0x5C, + 0x45 + }); + // VZW Package + callerIdFingerprints.add( + SIGNED_VZW, + new byte[] { + (byte) 0xE6, + 0x7A, + 0x0E, + (byte) 0xB0, + 0x76, + 0x4E, + (byte) 0xC3, + 0x28, + (byte) 0xB7, + (byte) 0xC1, + 0x1B, + 0x1B, + (byte) 0xD0, + (byte) 0x84, + 0x28, + (byte) 0xA6, + 0x16, + (byte) 0xD9, + (byte) 0xF3, + (byte) 0xEB, + (byte) 0xB0, + 0x20, + (byte) 0xA7, + (byte) 0xD8, + (byte) 0xDF, + 0x14, + 0x72, + (byte) 0x81, + 0x4C, + 0x13, + (byte) 0xF3, + (byte) 0xC9 + }); + + // Sprint Package + callerIdFingerprints.add( + SIGNED_SPRINT, + new byte[] { + 0x1A, + (byte) 0xBA, + (byte) 0xA2, + (byte) 0x84, + 0x0C, + 0x61, + (byte) 0x96, + 0x09, + (byte) 0x91, + 0x5E, + (byte) 0x91, + (byte) 0x95, + 0x3D, + 0x29, + 0x3C, + (byte) 0x90, + (byte) 0xEC, + (byte) 0xB4, + (byte) 0x89, + 0x1D, + (byte) 0xC0, + (byte) 0xB1, + 0x23, + 0x58, + (byte) 0x98, + (byte) 0xEB, + (byte) 0xE6, + (byte) 0xD4, + 0x09, + (byte) 0xE5, + (byte) 0x8E, + (byte) 0x9D + }); + } + + @SuppressLint("PackageManagerGetSignatures") + static boolean isCallerIdInstalled( + @Nullable PackageManager packageManager, @Nullable String authority) { + if (packageManager == null) { + LogUtil.i("CequintPackageUtils.isCallerIdInstalled", "failed to get PackageManager!"); + return false; + } + + ProviderInfo providerInfo = + packageManager.resolveContentProvider(authority, PackageManager.GET_META_DATA); + if (providerInfo == null) { + LogUtil.d( + "CequintPackageUtils.isCallerIdInstalled", + "no content provider with '%s' authority", + authority); + return false; + } + + String packageName = providerInfo.packageName; + if (packageName == null) { + LogUtil.w("CequintPackageUtils.isCallerIdInstalled", "can't get valid package name."); + return false; + } + + LogUtil.i( + "CequintPackageUtils.isCallerIdInstalled", + "content provider package name : " + packageName); + + try { + PackageInfo packageInfo = + packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + + Signature[] signatures = packageInfo.signatures; + if (signatures.length > 1) { + LogUtil.w( + "CequintPackageUtils.isCallerIdInstalled", "package has more than one signature."); + return false; + } + byte[] sha256Bytes = getSHA256(signatures[0].toByteArray()); + + for (int i = 0; i < callerIdFingerprints.size(); i++) { + if (Arrays.equals(callerIdFingerprints.get(i), sha256Bytes)) { + LogUtil.i( + "CequintPackageUtils.isCallerIdInstalled", + "this is %s Caller Name ID APK.", + getApkTypeString(i)); + return true; + } + } + } catch (PackageManager.NameNotFoundException e) { + LogUtil.e( + "CequintPackageUtils.isCallerIdInstalled", + "couldn't find package info for the package: %s", + packageName, + e); + } + LogUtil.w( + "CequintPackageUtils.isCallerIdInstalled", + "signature check failed for package: %s", + packageName); + return false; + } + + // Returns sha256 hash of the signature + @Nullable + private static byte[] getSHA256(byte[] sig) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA256", "BC"); + } catch (NoSuchAlgorithmException | NoSuchProviderException e) { + LogUtil.e("CequintPackageUtils.getSHA256", "", e); + return null; + } + + digest.update(sig); + return digest.digest(); + } + + private static String getApkTypeString(int index) { + switch (index) { + case SIGNED_1024: + return "1024-signed"; + case SIGNED_2048: + return "2048-signed"; + case SIGNED_VZW: + return "VZWPackage"; + case SIGNED_SPRINT: + default: + return "SprintPackage"; + } + } +} diff --git a/java/com/android/dialer/oem/res/values/motorola_config.xml b/java/com/android/dialer/oem/res/values/motorola_config.xml index 46e7a16b6..ba451e715 100644 --- a/java/com/android/dialer/oem/res/values/motorola_config.xml +++ b/java/com/android/dialer/oem/res/values/motorola_config.xml @@ -74,4 +74,10 @@ @string/motorola_hidden_menu_intent + + + + com.cequint.ecid + \ No newline at end of file -- cgit v1.2.3