Home | History | Annotate | Download | only in quickcontact
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.contacts.quickcontact;
     18 
     19 import com.android.contacts.ContactsUtils;
     20 import com.android.contacts.R;
     21 import com.android.contacts.model.AccountType.EditType;
     22 import com.android.contacts.model.DataKind;
     23 import com.android.contacts.util.Constants;
     24 import com.android.contacts.util.StructuredPostalUtils;
     25 import com.android.contacts.util.PhoneCapabilityTester;
     26 
     27 import android.content.ContentUris;
     28 import android.content.Context;
     29 import android.content.Intent;
     30 import android.content.pm.PackageManager;
     31 import android.database.Cursor;
     32 import android.graphics.drawable.Drawable;
     33 import android.net.Uri;
     34 import android.net.WebAddress;
     35 import android.provider.ContactsContract.CommonDataKinds.Email;
     36 import android.provider.ContactsContract.CommonDataKinds.Im;
     37 import android.provider.ContactsContract.CommonDataKinds.Phone;
     38 import android.provider.ContactsContract.CommonDataKinds.SipAddress;
     39 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
     40 import android.provider.ContactsContract.CommonDataKinds.Website;
     41 import android.provider.ContactsContract.Data;
     42 import android.text.TextUtils;
     43 import android.util.Log;
     44 
     45 /**
     46  * Description of a specific {@link Data#_ID} item, with style information
     47  * defined by a {@link DataKind}.
     48  */
     49 public class DataAction implements Action {
     50     private static final String TAG = "DataAction";
     51 
     52     private final Context mContext;
     53     private final DataKind mKind;
     54     private final String mMimeType;
     55 
     56     private CharSequence mBody;
     57     private CharSequence mSubtitle;
     58     private Intent mIntent;
     59     private Intent mAlternateIntent;
     60     private int mAlternateIconDescriptionRes;
     61     private int mAlternateIconRes;
     62 
     63     private Uri mDataUri;
     64     private long mDataId;
     65     private boolean mIsPrimary;
     66 
     67     /**
     68      * Create an action from common {@link Data} elements.
     69      */
     70     public DataAction(Context context, String mimeType, DataKind kind, long dataId, Cursor cursor) {
     71         mContext = context;
     72         mKind = kind;
     73         mMimeType = mimeType;
     74 
     75         // Determine type for subtitle
     76         mSubtitle = "";
     77         if (kind.typeColumn != null) {
     78             final int typeColumnIndex = cursor.getColumnIndex(kind.typeColumn);
     79             if (typeColumnIndex != -1) {
     80                 final int typeValue = cursor.getInt(typeColumnIndex);
     81 
     82                 // get type string
     83                 for (EditType type : kind.typeList) {
     84                     if (type.rawValue == typeValue) {
     85                         if (type.customColumn == null) {
     86                             // Non-custom type. Get its description from the resource
     87                             mSubtitle = context.getString(type.labelRes);
     88                         } else {
     89                             // Custom type. Read it from the database
     90                             mSubtitle = cursor.getString(cursor.getColumnIndexOrThrow(
     91                                     type.customColumn));
     92                         }
     93                         break;
     94                     }
     95                 }
     96             }
     97         }
     98 
     99         if (getAsInt(cursor, Data.IS_SUPER_PRIMARY) != 0) {
    100             mIsPrimary = true;
    101         }
    102 
    103         if (mKind.actionBody != null) {
    104             mBody = mKind.actionBody.inflateUsing(context, cursor);
    105         }
    106 
    107         mDataId = dataId;
    108         mDataUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
    109 
    110         final boolean hasPhone = PhoneCapabilityTester.isPhone(mContext);
    111         final boolean hasSms = PhoneCapabilityTester.isSmsIntentRegistered(mContext);
    112 
    113         // Handle well-known MIME-types with special care
    114         if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
    115             if (PhoneCapabilityTester.isPhone(mContext)) {
    116                 final String number = getAsString(cursor, Phone.NUMBER);
    117                 if (!TextUtils.isEmpty(number)) {
    118 
    119                     final Intent phoneIntent = hasPhone ? new Intent(Intent.ACTION_CALL_PRIVILEGED,
    120                             Uri.fromParts(Constants.SCHEME_TEL, number, null)) : null;
    121                     final Intent smsIntent = hasSms ? new Intent(Intent.ACTION_SENDTO,
    122                             Uri.fromParts(Constants.SCHEME_SMSTO, number, null)) : null;
    123 
    124                     // Configure Icons and Intents. Notice actionIcon is already set to the phone
    125                     if (hasPhone && hasSms) {
    126                         mIntent = phoneIntent;
    127                         mAlternateIntent = smsIntent;
    128                         mAlternateIconRes = kind.iconAltRes;
    129                         mAlternateIconDescriptionRes = kind.iconAltDescriptionRes;
    130                     } else if (hasPhone) {
    131                         mIntent = phoneIntent;
    132                     } else if (hasSms) {
    133                         mIntent = smsIntent;
    134                     }
    135                 }
    136             }
    137         } else if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType)) {
    138             if (PhoneCapabilityTester.isSipPhone(mContext)) {
    139                 final String address = getAsString(cursor, SipAddress.SIP_ADDRESS);
    140                 if (!TextUtils.isEmpty(address)) {
    141                     final Uri callUri = Uri.fromParts(Constants.SCHEME_SIP, address, null);
    142                     mIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, callUri);
    143                     // Note that this item will get a SIP-specific variant
    144                     // of the "call phone" icon, rather than the standard
    145                     // app icon for the Phone app (which we show for
    146                     // regular phone numbers.)  That's because the phone
    147                     // app explicitly specifies an android:icon attribute
    148                     // for the SIP-related intent-filters in its manifest.
    149                 }
    150             }
    151         } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
    152             final String address = getAsString(cursor, Email.DATA);
    153             if (!TextUtils.isEmpty(address)) {
    154                 final Uri mailUri = Uri.fromParts(Constants.SCHEME_MAILTO, address, null);
    155                 mIntent = new Intent(Intent.ACTION_SENDTO, mailUri);
    156             }
    157 
    158         } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType)) {
    159             final String url = getAsString(cursor, Website.URL);
    160             if (!TextUtils.isEmpty(url)) {
    161                 WebAddress webAddress = new WebAddress(url);
    162                 mIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(webAddress.toString()));
    163             }
    164 
    165         } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType)) {
    166             final boolean isEmail = Email.CONTENT_ITEM_TYPE.equals(
    167                     getAsString(cursor, Data.MIMETYPE));
    168             if (isEmail || isProtocolValid(cursor)) {
    169                 final int protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK :
    170                         getAsInt(cursor, Im.PROTOCOL);
    171 
    172                 if (isEmail) {
    173                     // Use Google Talk string when using Email, and clear data
    174                     // Uri so we don't try saving Email as primary.
    175                     mSubtitle = context.getText(R.string.chat_gtalk);
    176                     mDataUri = null;
    177                 }
    178 
    179                 String host = getAsString(cursor, Im.CUSTOM_PROTOCOL);
    180                 String data = getAsString(cursor,
    181                         isEmail ? Email.DATA : Im.DATA);
    182                 if (protocol != Im.PROTOCOL_CUSTOM) {
    183                     // Try bringing in a well-known host for specific protocols
    184                     host = ContactsUtils.lookupProviderNameFromId(protocol);
    185                 }
    186 
    187                 if (!TextUtils.isEmpty(host) && !TextUtils.isEmpty(data)) {
    188                     final String authority = host.toLowerCase();
    189                     final Uri imUri = new Uri.Builder().scheme(Constants.SCHEME_IMTO).authority(
    190                             authority).appendPath(data).build();
    191                     mIntent = new Intent(Intent.ACTION_SENDTO, imUri);
    192 
    193                     // If the address is also available for a video chat, we'll show the capability
    194                     // as a secondary action.
    195                     final int chatCapability = getAsInt(cursor, Data.CHAT_CAPABILITY);
    196                     final boolean isVideoChatCapable =
    197                             (chatCapability & Im.CAPABILITY_HAS_CAMERA) != 0;
    198                     final boolean isAudioChatCapable =
    199                             (chatCapability & Im.CAPABILITY_HAS_VOICE) != 0;
    200                     if (isVideoChatCapable || isAudioChatCapable) {
    201                         mAlternateIntent = new Intent(
    202                                 Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?call"));
    203                         if (isVideoChatCapable) {
    204                             mAlternateIconRes = R.drawable.sym_action_videochat_holo_light;
    205                             mAlternateIconDescriptionRes = R.string.video_chat;
    206                         } else {
    207                             mAlternateIconRes = R.drawable.sym_action_audiochat_holo_light;
    208                             mAlternateIconDescriptionRes = R.string.audio_chat;
    209                         }
    210                     }
    211                 }
    212             }
    213         } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType)) {
    214             final String postalAddress = getAsString(cursor, StructuredPostal.FORMATTED_ADDRESS);
    215             if (!TextUtils.isEmpty(postalAddress)) {
    216                 mIntent = StructuredPostalUtils.getViewPostalAddressIntent(postalAddress);
    217             }
    218         }
    219 
    220         if (mIntent == null) {
    221             // Otherwise fall back to default VIEW action
    222             mIntent = new Intent(Intent.ACTION_VIEW);
    223             mIntent.setDataAndType(mDataUri, mimeType);
    224         }
    225 
    226         // Always launch as new task, since we're like a launcher
    227         mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
    228     }
    229 
    230     /** Read {@link String} from the given {@link Cursor}. */
    231     private static String getAsString(Cursor cursor, String columnName) {
    232         final int index = cursor.getColumnIndex(columnName);
    233         return cursor.getString(index);
    234     }
    235 
    236     /** Read {@link Integer} from the given {@link Cursor}. */
    237     private static int getAsInt(Cursor cursor, String columnName) {
    238         final int index = cursor.getColumnIndex(columnName);
    239         return cursor.getInt(index);
    240     }
    241 
    242     private boolean isProtocolValid(Cursor cursor) {
    243         final int columnIndex = cursor.getColumnIndex(Im.PROTOCOL);
    244         if (cursor.isNull(columnIndex)) {
    245             return false;
    246         }
    247         try {
    248             Integer.valueOf(cursor.getString(columnIndex));
    249         } catch (NumberFormatException e) {
    250             return false;
    251         }
    252         return true;
    253     }
    254 
    255     @Override
    256     public CharSequence getSubtitle() {
    257         return mSubtitle;
    258     }
    259 
    260     @Override
    261     public CharSequence getBody() {
    262         return mBody;
    263     }
    264 
    265     @Override
    266     public String getMimeType() {
    267         return mMimeType;
    268     }
    269 
    270     @Override
    271     public Uri getDataUri() {
    272         return mDataUri;
    273     }
    274 
    275     @Override
    276     public long getDataId() {
    277         return mDataId;
    278     }
    279 
    280     @Override
    281     public Boolean isPrimary() {
    282         return mIsPrimary;
    283     }
    284 
    285     @Override
    286     public Drawable getAlternateIcon() {
    287         if (mAlternateIconRes == 0) return null;
    288 
    289         final String resPackageName = mKind.resPackageName;
    290         if (resPackageName == null) {
    291             return mContext.getResources().getDrawable(mAlternateIconRes);
    292         }
    293 
    294         final PackageManager pm = mContext.getPackageManager();
    295         return pm.getDrawable(resPackageName, mAlternateIconRes, null);
    296     }
    297 
    298     @Override
    299     public String getAlternateIconDescription() {
    300         if (mAlternateIconDescriptionRes == 0) return null;
    301         return mContext.getResources().getString(mAlternateIconDescriptionRes);
    302     }
    303 
    304     @Override
    305     public Intent getIntent() {
    306         return mIntent;
    307     }
    308 
    309     @Override
    310     public Intent getAlternateIntent() {
    311         return mAlternateIntent;
    312     }
    313 
    314     @Override
    315     public boolean collapseWith(Action other) {
    316         if (!shouldCollapseWith(other)) {
    317             return false;
    318         }
    319         return true;
    320     }
    321 
    322     @Override
    323     public boolean shouldCollapseWith(Action t) {
    324         if (t == null) {
    325             return false;
    326         }
    327         if (!(t instanceof DataAction)) {
    328             Log.e(TAG, "t must be DataAction");
    329             return false;
    330         }
    331         DataAction that = (DataAction)t;
    332         if (!ContactsUtils.shouldCollapse(mMimeType, mBody, that.mMimeType, that.mBody)) {
    333             return false;
    334         }
    335         if (!TextUtils.equals(mMimeType, that.mMimeType)
    336                 || !ContactsUtils.areIntentActionEqual(mIntent, that.mIntent)) {
    337             return false;
    338         }
    339         return true;
    340     }
    341 }
    342