package ru.henridellal.dialer; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.graphics.Typeface; import android.net.Uri; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.telephony.PhoneNumberUtils; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Filter; import android.widget.Filterable; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.regex.*; import ru.henridellal.dialer.AsyncContactImageLoader.ImageCallback; public class ContactsEntryAdapter extends BaseAdapter implements Filterable, View.OnClickListener { public static final String[] PROJECTION = { Phone._ID, Phone.LOOKUP_KEY, Phone.DISPLAY_NAME, Phone.NUMBER }; private static final int[] t9NumberPatternIds = new int[]{ R.string.regex_0, R.string.regex_1, R.string.regex_2, R.string.regex_3, R.string.regex_4, R.string.regex_5, R.string.regex_6, R.string.regex_7, R.string.regex_8, R.string.regex_9 }; private static final int COLUMN_LOOKUP_KEY = 1; private static final int COLUMN_NAME = 2; private static final int COLUMN_NUMBER = 3; private ForegroundColorSpan span; private StyleSpan boldStyleSpan; private Map<Character, String> t9NumberPatterns; private ArrayList<RegexQueryResult> regexQueryResults; private AsyncContactImageLoader mAsyncContactImageLoader; private Cursor mCursor; private ContactsFilter mFilter; private boolean rawFiltering; private SoftReference<DialerActivity> activityRef; public ContactsEntryAdapter(DialerActivity activity, AsyncContactImageLoader asyncContactImageLoader, Context t9LocaleContext) { super(); activityRef = new SoftReference<DialerActivity>(activity); initT9NumberPatterns(null != t9LocaleContext ? t9LocaleContext.getResources() : activityRef.get().getResources()); regexQueryResults = new ArrayList<RegexQueryResult>(); mAsyncContactImageLoader = asyncContactImageLoader; span = new ForegroundColorSpan(activity.getResources().getColor(R.color.green_600)); boldStyleSpan = new StyleSpan(Typeface.BOLD); } private void initT9NumberPatterns(Resources res) { t9NumberPatterns = new HashMap<Character, String>(); for (char i = '0'; i <= '9'; i++) { t9NumberPatterns.put(new Character(i), res.getString(t9NumberPatternIds[Character.getNumericValue(i)])); } t9NumberPatterns.put(new Character('*'), res.getString(R.string.regex_star)); t9NumberPatterns.put(new Character('#'), res.getString(R.string.regex_hash)); t9NumberPatterns.put(new Character('+'), Pattern.quote("+")); } public void setCursor(Cursor cursor) { if (mCursor != null) { mCursor.close(); } mCursor = cursor; } @Override public Filter getFilter() { if (mFilter == null) { mFilter = new ContactsFilter(); } return mFilter; } public void resetFilter() { mFilter = null; if (null != regexQueryResults) regexQueryResults.clear(); } @Override public int getCount() { return (null != regexQueryResults) ? regexQueryResults.size() : 0; } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } @Override public void onClick(View view) { if (view.getId() == R.id.contact_entry_image) { Intent intent = new Intent(Intent.ACTION_VIEW); Uri uri = Uri.withAppendedPath(Phone.CONTENT_URI, ((ContactImageTag)view.getTag()).contactId); intent.setDataAndType(uri, "vnd.android.cursor.dir/contact"); try { activityRef.get().startActivity(intent); } catch (ActivityNotFoundException e) { activityRef.get().showMissingContactsAppDialog(); } } } @Override public View getView(int position, View convertView, ViewGroup parent) { View view; if (convertView == null) { view = LayoutInflater.from(activityRef.get()).inflate(R.layout.contacts_entry, parent, false); view.setTag(new ContactsEntryCache(view)); } else { view = convertView; } final RegexQueryResult queryResult = regexQueryResults.get(position); final ContactsEntryCache viewCache = (ContactsEntryCache) view.getTag(); mCursor.moveToPosition(queryResult.position); String name = mCursor.getString(COLUMN_NAME); if (!TextUtils.isEmpty(name) && queryResult.start != queryResult.end) { SpannableString nameSpanned = new SpannableString(name); nameSpanned.setSpan(span, queryResult.start, queryResult.end, 0); nameSpanned.setSpan(boldStyleSpan, queryResult.start, queryResult.end, 0); viewCache.contactName.setText(nameSpanned); } else { viewCache.contactName.setText(name); } String phoneNumber = mCursor.getString(COLUMN_NUMBER); if (!TextUtils.isEmpty(phoneNumber) && queryResult.numberStart != queryResult.numberEnd) { SpannableString numberSpanned = new SpannableString(formatNumber(phoneNumber)); numberSpanned.setSpan(span, queryResult.numberStart, queryResult.numberEnd, 0); viewCache.phoneNumber.setText(numberSpanned); } else { viewCache.phoneNumber.setText(formatNumber(phoneNumber)); } String lookupKey = mCursor.getString(COLUMN_LOOKUP_KEY); ContactImageTag tag = new ContactImageTag(String.valueOf(mCursor.getInt(0)), lookupKey); viewCache.contactImage.setTag(tag); // set a tag for the callback to be able to check, so we don't set the contact image of a reused view Drawable d = mAsyncContactImageLoader.loadDrawableForContact(lookupKey, new ImageCallback() { @Override public void imageLoaded(Drawable imageDrawable, String lookupKey) { if (lookupKey.equals(((ContactImageTag)viewCache.contactImage.getTag()).lookupKey)) { viewCache.contactImage.setImageDrawable(imageDrawable); } } }); viewCache.contactImage.setImageDrawable(d); /*String thumbnailUri = mCursor.getString(COLUMN_THUMBNAIL_URI); if ( null == thumbnailUri) { viewCache.contactImage.setImageDrawable(null); } else { try { InputStream input = activityRef.get().getContentResolver().openInputStream(Uri.parse(thumbnailUri)); Drawable mDrawable = Drawable.createFromStream(input, thumbnailUri); viewCache.contactImage.setImageDrawable(mDrawable); input.close(); } catch (Exception e) { viewCache.contactImage.setImageDrawable(null); } }*/ viewCache.contactImage.setOnClickListener(this); return view; } public void setRawFiltering(boolean rawFiltering) { this.rawFiltering = rawFiltering; } public String getPhoneNumber(int position) { mCursor.moveToPosition(regexQueryResults.get(position).position); return mCursor.getString(COLUMN_NUMBER); } public String formatNumber(String number) { String formattedNumber = PhoneNumberUtils.formatNumber(number, Locale.getDefault().getCountry()); return (null != formattedNumber) ? formattedNumber : null; } public String formContactNameRegex(String s) { StringBuilder result = new StringBuilder(); char mChar; for (int i = 0; i < s.length(); i++) { mChar = s.charAt(i); result.append(t9NumberPatterns.get(mChar)); } return result.toString(); } private boolean isRegexIdentifier(char c) { return c == '*' || c == '#' || c == '+'; } public String formNumberRegex(String s) { StringBuilder result = new StringBuilder(); char numberChar = s.charAt(0); result.append(isRegexIdentifier(numberChar)? Pattern.quote(Character.toString(numberChar)) : numberChar); for (int i = 1; i < s.length(); i++) { result.append("[\\W]*"); numberChar = s.charAt(i); result.append(isRegexIdentifier(numberChar) ? Pattern.quote(Character.toString(numberChar)) : numberChar); } return result.toString(); } private class ContactsFilter extends Filter { private void filterRaw(ArrayList<RegexQueryResult> resultsList, CharSequence constraint) { mCursor.moveToFirst(); String constraintString = constraint.toString().toLowerCase(); while (!mCursor.isAfterLast()) { String name = mCursor.getString(COLUMN_NAME); String number = mCursor.getString(COLUMN_NUMBER); RegexQueryResult queryResult = null; if (null != name) { name = name.toLowerCase(); int nameIndexOfConstraint = name.indexOf(constraintString); if (nameIndexOfConstraint != -1) queryResult = new RegexQueryResult(mCursor.getPosition(), nameIndexOfConstraint, nameIndexOfConstraint+name.length()); } if (null != number) { number = number.toLowerCase(); int numberIndexOfConstraint = number.indexOf(constraintString); if (numberIndexOfConstraint != -1) { if (null == queryResult) { queryResult = new RegexQueryResult(mCursor.getPosition(), 0, 0); } queryResult.setNumberPlace(numberIndexOfConstraint, numberIndexOfConstraint+constraintString.length()); } } if (null != queryResult) { resultsList.add(queryResult); } mCursor.moveToNext(); } } private void filterWithRegex(ArrayList<RegexQueryResult> resultsList, CharSequence constraint) { String nameRegex = formContactNameRegex(constraint.toString()); String numberRegex = formNumberRegex(constraint.toString()); Pattern namePattern = Pattern.compile(nameRegex); Pattern wordStartPattern = Pattern.compile("\\s+" + nameRegex); Pattern numberPattern = Pattern.compile(numberRegex); mCursor.moveToFirst(); while (!mCursor.isAfterLast()) { String name = mCursor.getString(COLUMN_NAME); String number = mCursor.getString(COLUMN_NUMBER); Matcher nameMatcher = (null != name) ? namePattern.matcher(name) : null; Matcher numberMatcher = (null != number) ? numberPattern.matcher(formatNumber(number)) : null; RegexQueryResult queryResult = null; if (null != nameMatcher && nameMatcher.find()) { if (nameMatcher.start() == 0) { queryResult = new RegexQueryResult( mCursor.getPosition(), nameMatcher.start(), nameMatcher.end()); } else { Matcher wordStartMatcher = wordStartPattern.matcher(name); if (wordStartMatcher.find()) { queryResult = new RegexQueryResult( mCursor.getPosition(), wordStartMatcher.start(), wordStartMatcher.end()); } } } if (null != numberMatcher && numberMatcher.find()) { if (null == queryResult) { queryResult = new RegexQueryResult( mCursor.getPosition(), 256+numberMatcher.start(), 256+numberMatcher.start()); } queryResult.setNumberPlace(numberMatcher.start(), numberMatcher.end()); } if (null != queryResult) { resultsList.add(queryResult); } mCursor.moveToNext(); } } @Override protected Filter.FilterResults performFiltering(CharSequence constraint) { Filter.FilterResults results = new FilterResults(); ArrayList<RegexQueryResult> resultsList = new ArrayList<RegexQueryResult>(); if (rawFiltering) { filterRaw(resultsList, constraint); } else { filterWithRegex(resultsList, constraint); } Collections.sort(resultsList); results.values = resultsList; results.count = resultsList.size(); return results; } protected void publishResults(CharSequence constraint, Filter.FilterResults results) { regexQueryResults = ((ArrayList<RegexQueryResult>) results.values); notifyDataSetChanged(); } } }