Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2008 Esmertec AG.
      3  * Copyright (C) 2008 The Android Open Source Project
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.mms.ui;
     19 
     20 import com.android.mms.MmsApp;
     21 import com.android.mms.R;
     22 import com.android.mms.data.Contact;
     23 
     24 import android.content.ContentResolver;
     25 import android.content.Context;
     26 import android.database.Cursor;
     27 import android.database.MatrixCursor;
     28 import android.database.MergeCursor;
     29 import android.net.Uri;
     30 import android.provider.ContactsContract.Contacts;
     31 import android.provider.ContactsContract.CommonDataKinds.Phone;
     32 import android.provider.ContactsContract.DataUsageFeedback;
     33 import android.telephony.PhoneNumberUtils;
     34 import android.text.Annotation;
     35 import android.text.Spannable;
     36 import android.text.SpannableString;
     37 import android.text.TextUtils;
     38 import android.view.View;
     39 import android.widget.ResourceCursorAdapter;
     40 import android.widget.TextView;
     41 
     42 /**
     43  * This adapter is used to filter contacts on both name and number.
     44  */
     45 public class RecipientsAdapter extends ResourceCursorAdapter {
     46 
     47     public static final int CONTACT_ID_INDEX = 1;
     48     public static final int TYPE_INDEX       = 2;
     49     public static final int NUMBER_INDEX     = 3;
     50     public static final int LABEL_INDEX      = 4;
     51     public static final int NAME_INDEX       = 5;
     52     public static final int NORMALIZED_NUMBER = 6;
     53 
     54     private static final String[] PROJECTION_PHONE = {
     55         Phone._ID,                  // 0
     56         Phone.CONTACT_ID,           // 1
     57         Phone.TYPE,                 // 2
     58         Phone.NUMBER,               // 3
     59         Phone.LABEL,                // 4
     60         Phone.DISPLAY_NAME,         // 5
     61         Phone.NORMALIZED_NUMBER,    // 6
     62     };
     63 
     64     private static final String SORT_ORDER = Contacts.TIMES_CONTACTED + " DESC,"
     65             + Contacts.DISPLAY_NAME + "," + Phone.TYPE;
     66 
     67     private final Context mContext;
     68     private final ContentResolver mContentResolver;
     69     private final String mDefaultCountryIso;
     70 
     71     public RecipientsAdapter(Context context) {
     72         // Note that the RecipientsAdapter doesn't support auto-requeries. If we
     73         // want to respond to changes in the contacts we're displaying in the drop-down,
     74         // code using this adapter would have to add a line such as:
     75         //   mRecipientsAdapter.setOnDataSetChangedListener(mDataSetChangedListener);
     76         // See ComposeMessageActivity for an example.
     77         super(context, R.layout.recipient_filter_item, null, false /* no auto-requery */);
     78         mContext = context;
     79         mContentResolver = context.getContentResolver();
     80         mDefaultCountryIso = MmsApp.getApplication().getCurrentCountryIso();
     81     }
     82 
     83     @Override
     84     public final CharSequence convertToString(Cursor cursor) {
     85         String number = cursor.getString(RecipientsAdapter.NUMBER_INDEX);
     86         if (number == null) {
     87             return "";
     88         }
     89         number = number.trim();
     90 
     91         String name = cursor.getString(RecipientsAdapter.NAME_INDEX);
     92         int type = cursor.getInt(RecipientsAdapter.TYPE_INDEX);
     93 
     94         String label = cursor.getString(RecipientsAdapter.LABEL_INDEX);
     95         CharSequence displayLabel = Phone.getDisplayLabel(mContext, type, label);
     96 
     97         if (name == null) {
     98             name = "";
     99         } else {
    100             // Names with commas are the bane of the recipient editor's existence.
    101             // We've worked around them by using spans, but there are edge cases
    102             // where the spans get deleted. Furthermore, having commas in names
    103             // can be confusing to the user since commas are used as separators
    104             // between recipients. The best solution is to simply remove commas
    105             // from names.
    106             name = name.replace(", ", " ")
    107                        .replace(",", " ");  // Make sure we leave a space between parts of names.
    108         }
    109 
    110         String nameAndNumber = Contact.formatNameAndNumber( name, number,
    111                 cursor.getString(NORMALIZED_NUMBER));
    112 
    113         SpannableString out = new SpannableString(nameAndNumber);
    114         int len = out.length();
    115 
    116         if (!TextUtils.isEmpty(name)) {
    117             out.setSpan(new Annotation("name", name), 0, len,
    118                         Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    119         } else {
    120             out.setSpan(new Annotation("name", number), 0, len,
    121                         Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    122         }
    123 
    124         String person_id = cursor.getString(RecipientsAdapter.CONTACT_ID_INDEX);
    125         out.setSpan(new Annotation("person_id", person_id), 0, len,
    126                     Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    127         out.setSpan(new Annotation("label", displayLabel.toString()), 0, len,
    128                     Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    129         out.setSpan(new Annotation("number", number), 0, len,
    130                     Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    131 
    132         return out;
    133     }
    134 
    135     @Override
    136     public final void bindView(View view, Context context, Cursor cursor) {
    137         TextView name = (TextView) view.findViewById(R.id.name);
    138         name.setText(cursor.getString(NAME_INDEX));
    139 
    140         TextView label = (TextView) view.findViewById(R.id.label);
    141         int type = cursor.getInt(TYPE_INDEX);
    142         CharSequence labelText = Phone.getDisplayLabel(mContext, type,
    143                 cursor.getString(LABEL_INDEX));
    144         // When there's no label, getDisplayLabel() returns a CharSequence of length==1 containing
    145         // a unicode non-breaking space. Need to check for that and consider that as "no label".
    146         if (labelText.length() == 0 ||
    147                 (labelText.length() == 1 && labelText.charAt(0) == '\u00A0')) {
    148             label.setVisibility(View.GONE);
    149         } else {
    150             label.setText(labelText);
    151             label.setVisibility(View.VISIBLE);
    152         }
    153 
    154         TextView number = (TextView) view.findViewById(R.id.number);
    155         number.setText(
    156                 PhoneNumberUtils.formatNumber(cursor.getString(NUMBER_INDEX),
    157                         cursor.getString(NORMALIZED_NUMBER), mDefaultCountryIso));
    158     }
    159 
    160     @Override
    161     public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
    162         String phone = "";
    163         String cons = null;
    164 
    165         if (constraint != null) {
    166             cons = constraint.toString();
    167 
    168             if (usefulAsDigits(cons)) {
    169                 phone = PhoneNumberUtils.convertKeypadLettersToDigits(cons);
    170                 if (phone.equals(cons)) {
    171                     phone = "";
    172                 } else {
    173                     phone = phone.trim();
    174                 }
    175             }
    176         }
    177 
    178         Uri uri = Phone.CONTENT_FILTER_URI.buildUpon()
    179                 .appendPath(cons)
    180                 .appendQueryParameter(DataUsageFeedback.USAGE_TYPE,
    181                         DataUsageFeedback.USAGE_TYPE_SHORT_TEXT)
    182                 .build();
    183         /*
    184          * if we decide to filter based on phone types use a selection
    185          * like this.
    186         String selection = String.format("%s=%s OR %s=%s OR %s=%s",
    187                 Phone.TYPE,
    188                 Phone.TYPE_MOBILE,
    189                 Phone.TYPE,
    190                 Phone.TYPE_WORK_MOBILE,
    191                 Phone.TYPE,
    192                 Phone.TYPE_MMS);
    193          */
    194         Cursor phoneCursor =
    195             mContentResolver.query(uri,
    196                     PROJECTION_PHONE,
    197                     null, //selection,
    198                     null,
    199                     null);
    200 
    201         if (phone.length() > 0) {
    202             Object[] result = new Object[7];
    203             result[0] = Integer.valueOf(-1);                    // ID
    204             result[1] = Long.valueOf(-1);                       // CONTACT_ID
    205             result[2] = Integer.valueOf(Phone.TYPE_CUSTOM);     // TYPE
    206             result[3] = phone;                                  // NUMBER
    207 
    208             /*
    209              * The "\u00A0" keeps Phone.getDisplayLabel() from deciding
    210              * to display the default label ("Home") next to the transformation
    211              * of the letters into numbers.
    212              */
    213             result[4] = "\u00A0";                               // LABEL
    214             result[5] = cons;                                   // NAME
    215             result[6] = phone;                                  // NORMALIZED_NUMBER
    216 
    217             MatrixCursor translated = new MatrixCursor(PROJECTION_PHONE, 1);
    218             translated.addRow(result);
    219             return new MergeCursor(new Cursor[] { translated, phoneCursor });
    220         } else {
    221             return phoneCursor;
    222         }
    223     }
    224 
    225     /**
    226      * Returns true if all the characters are meaningful as digits
    227      * in a phone number -- letters, digits, and a few punctuation marks.
    228      */
    229     private boolean usefulAsDigits(CharSequence cons) {
    230         int len = cons.length();
    231 
    232         for (int i = 0; i < len; i++) {
    233             char c = cons.charAt(i);
    234 
    235             if ((c >= '0') && (c <= '9')) {
    236                 continue;
    237             }
    238             if ((c == ' ') || (c == '-') || (c == '(') || (c == ')') || (c == '.') || (c == '+')
    239                     || (c == '#') || (c == '*')) {
    240                 continue;
    241             }
    242             if ((c >= 'A') && (c <= 'Z')) {
    243                 continue;
    244             }
    245             if ((c >= 'a') && (c <= 'z')) {
    246                 continue;
    247             }
    248 
    249             return false;
    250         }
    251 
    252         return true;
    253     }
    254 }
    255