/* * Copyright (C) 2015 Samsung System LSI * 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.bluetooth.map; import java.util.Arrays; import java.util.HashMap; import java.util.regex.Pattern; import android.annotation.TargetApi; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.PhoneLookup; import android.provider.Telephony.CanonicalAddressesColumns; import android.provider.Telephony.MmsSms; import android.util.Log; /** * Use these functions when extracting data for listings. It caches frequently used data to * speed up building large listings - e.g. before applying filtering. */ @TargetApi(19) public class SmsMmsContacts { private static final String TAG = "SmsMmsContacts"; private HashMap mPhoneNumbers = null; private final HashMap mNames = new HashMap(10); private static final Uri ADDRESS_URI = MmsSms.CONTENT_URI.buildUpon().appendPath("canonical-addresses").build(); private static final String[] ADDRESS_PROJECTION = { CanonicalAddressesColumns._ID, CanonicalAddressesColumns.ADDRESS }; private static final int COL_ADDR_ID = Arrays.asList(ADDRESS_PROJECTION).indexOf(CanonicalAddressesColumns._ID); private static final int COL_ADDR_ADDR = Arrays.asList(ADDRESS_PROJECTION).indexOf(CanonicalAddressesColumns.ADDRESS); private static final String[] CONTACT_PROJECTION = {Contacts._ID, Contacts.DISPLAY_NAME}; private static final String CONTACT_SEL_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1"; private static final int COL_CONTACT_ID = Arrays.asList(CONTACT_PROJECTION).indexOf(Contacts._ID); private static final int COL_CONTACT_NAME = Arrays.asList(CONTACT_PROJECTION).indexOf(Contacts.DISPLAY_NAME); /** * Get a contacts phone number based on the canonical addresses id of the contact. * (The ID listed in the Threads table.) * @param resolver the ContantResolver to be used. * @param id the id of the contact, as listed in the Threads table * @return the phone number of the contact - or null if id does not exist. */ public String getPhoneNumber(ContentResolver resolver, long id) { String number; if(mPhoneNumbers != null && (number = mPhoneNumbers.get(id)) != null) { return number; } fillPhoneCache(resolver); return mPhoneNumbers.get(id); } public static String getPhoneNumberUncached(ContentResolver resolver, long id) { String where = CanonicalAddressesColumns._ID + " = " + id; Cursor c = resolver.query(ADDRESS_URI, ADDRESS_PROJECTION, where, null, null); try { if (c != null) { if(c.moveToPosition(0)) { return c.getString(COL_ADDR_ADDR); } } Log.e(TAG, "query failed"); } finally { if(c != null) c.close(); } return null; } /** * Clears the local cache. Call after a listing is complete, to avoid using invalid data. */ public void clearCache() { if(mPhoneNumbers != null) mPhoneNumbers.clear(); if(mNames != null) mNames.clear(); } /** * Refreshes the cache, by clearing all cached values and fill the cache with the result of * a new query. * @param resolver the ContantResolver to be used. */ private void fillPhoneCache(ContentResolver resolver){ Cursor c = resolver.query(ADDRESS_URI, ADDRESS_PROJECTION, null, null, null); if(mPhoneNumbers == null) { int size = 0; if(c != null) { size = c.getCount(); } mPhoneNumbers = new HashMap(size); } else { mPhoneNumbers.clear(); } try { if (c != null) { long id; String addr; c.moveToPosition(-1); while (c.moveToNext()) { id = c.getLong(COL_ADDR_ID); addr = c.getString(COL_ADDR_ADDR); mPhoneNumbers.put(id, addr); } } else { Log.e(TAG, "query failed"); } } finally { if(c != null) c.close(); } } public MapContact getContactNameFromPhone(String phone, ContentResolver resolver) { return getContactNameFromPhone(phone, resolver, null); } /** * Lookup a contacts name in the Android Contacts database. * @param phone the phone number of the contact * @param resolver the ContentResolver to use. * @return the name of the contact or null, if no contact was found. */ public MapContact getContactNameFromPhone(String phone, ContentResolver resolver, String contactNameFilter) { MapContact contact = mNames.get(phone); if(contact != null){ if(contact.getId() < 0) { return null; } if(contactNameFilter == null) { return contact; } // Validate filter String searchString = contactNameFilter.replace("*", ".*"); searchString = ".*" + searchString + ".*"; Pattern p = Pattern.compile(Pattern.quote(searchString), Pattern.CASE_INSENSITIVE); if(p.matcher(contact.getName()).find()) { return contact; } return null; } // TODO: Should we change to extract both formatted name, and display name? Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,Uri.encode(phone)); String selection = CONTACT_SEL_VISIBLE; String[] selectionArgs = null; if(contactNameFilter != null) { selection += "AND " + ContactsContract.Contacts.DISPLAY_NAME + " like ?"; selectionArgs = new String[]{"%" + contactNameFilter.replace("*", "%") + "%"}; } Cursor c = resolver.query(uri, CONTACT_PROJECTION, selection, selectionArgs, null); try { if (c != null && c.getCount() >= 1) { c.moveToFirst(); long id = c.getLong(COL_CONTACT_ID); String name = c.getString(COL_CONTACT_NAME); contact = MapContact.create(id, name); mNames.put(phone, contact); } else { contact = MapContact.create(-1, null); mNames.put(phone, contact); contact = null; } } finally { if (c != null) c.close(); } return contact; } }