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.PhoneCapabilityTester; 25 import com.android.contacts.util.StructuredPostalUtils; 26 27 import android.content.ContentUris; 28 import android.content.ContentValues; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.pm.PackageManager; 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 private int mPresence = -1; 63 64 private Uri mDataUri; 65 private long mDataId; 66 private boolean mIsPrimary; 67 68 /** 69 * Create an action from common {@link Data} elements. 70 */ 71 public DataAction(Context context, String mimeType, DataKind kind, long dataId, 72 ContentValues entryValues) { 73 mContext = context; 74 mKind = kind; 75 mMimeType = mimeType; 76 77 // Determine type for subtitle 78 mSubtitle = ""; 79 if (kind.typeColumn != null) { 80 if (entryValues.containsKey(kind.typeColumn)) { 81 final int typeValue = entryValues.getAsInteger(kind.typeColumn); 82 83 // get type string 84 for (EditType type : kind.typeList) { 85 if (type.rawValue == typeValue) { 86 if (type.customColumn == null) { 87 // Non-custom type. Get its description from the resource 88 mSubtitle = context.getString(type.labelRes); 89 } else { 90 // Custom type. Read it from the database 91 mSubtitle = entryValues.getAsString(type.customColumn); 92 } 93 break; 94 } 95 } 96 } 97 } 98 99 final Integer superPrimary = entryValues.getAsInteger(Data.IS_SUPER_PRIMARY); 100 mIsPrimary = superPrimary != null && superPrimary != 0; 101 102 if (mKind.actionBody != null) { 103 mBody = mKind.actionBody.inflateUsing(context, entryValues); 104 } 105 106 mDataId = dataId; 107 mDataUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId); 108 109 final boolean hasPhone = PhoneCapabilityTester.isPhone(mContext); 110 final boolean hasSms = PhoneCapabilityTester.isSmsIntentRegistered(mContext); 111 112 // Handle well-known MIME-types with special care 113 if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) { 114 if (PhoneCapabilityTester.isPhone(mContext)) { 115 final String number = entryValues.getAsString(Phone.NUMBER); 116 if (!TextUtils.isEmpty(number)) { 117 118 final Intent phoneIntent = hasPhone ? ContactsUtils.getCallIntent(number) 119 : null; 120 final Intent smsIntent = hasSms ? new Intent(Intent.ACTION_SENDTO, 121 Uri.fromParts(Constants.SCHEME_SMSTO, number, null)) : null; 122 123 // Configure Icons and Intents. Notice actionIcon is already set to the phone 124 if (hasPhone && hasSms) { 125 mIntent = phoneIntent; 126 mAlternateIntent = smsIntent; 127 mAlternateIconRes = kind.iconAltRes; 128 mAlternateIconDescriptionRes = kind.iconAltDescriptionRes; 129 } else if (hasPhone) { 130 mIntent = phoneIntent; 131 } else if (hasSms) { 132 mIntent = smsIntent; 133 } 134 } 135 } 136 } else if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType)) { 137 if (PhoneCapabilityTester.isSipPhone(mContext)) { 138 final String address = entryValues.getAsString(SipAddress.SIP_ADDRESS); 139 if (!TextUtils.isEmpty(address)) { 140 final Uri callUri = Uri.fromParts(Constants.SCHEME_SIP, address, null); 141 mIntent = ContactsUtils.getCallIntent(callUri); 142 // Note that this item will get a SIP-specific variant 143 // of the "call phone" icon, rather than the standard 144 // app icon for the Phone app (which we show for 145 // regular phone numbers.) That's because the phone 146 // app explicitly specifies an android:icon attribute 147 // for the SIP-related intent-filters in its manifest. 148 } 149 } 150 } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) { 151 final String address = entryValues.getAsString(Email.DATA); 152 if (!TextUtils.isEmpty(address)) { 153 final Uri mailUri = Uri.fromParts(Constants.SCHEME_MAILTO, address, null); 154 mIntent = new Intent(Intent.ACTION_SENDTO, mailUri); 155 } 156 157 } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType)) { 158 final String url = entryValues.getAsString(Website.URL); 159 if (!TextUtils.isEmpty(url)) { 160 WebAddress webAddress = new WebAddress(url); 161 mIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(webAddress.toString())); 162 } 163 164 } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType)) { 165 final boolean isEmail = Email.CONTENT_ITEM_TYPE.equals( 166 entryValues.getAsString(Data.MIMETYPE)); 167 if (isEmail || isProtocolValid(entryValues)) { 168 final int protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK : 169 entryValues.getAsInteger(Im.PROTOCOL); 170 171 if (isEmail) { 172 // Use Google Talk string when using Email, and clear data 173 // Uri so we don't try saving Email as primary. 174 mSubtitle = Im.getProtocolLabel(context.getResources(), Im.PROTOCOL_GOOGLE_TALK, 175 null); 176 mDataUri = null; 177 } 178 179 String host = entryValues.getAsString(Im.CUSTOM_PROTOCOL); 180 String data = entryValues.getAsString(isEmail ? Email.DATA : Im.DATA); 181 if (protocol != Im.PROTOCOL_CUSTOM) { 182 // Try bringing in a well-known host for specific protocols 183 host = ContactsUtils.lookupProviderNameFromId(protocol); 184 } 185 186 if (!TextUtils.isEmpty(host) && !TextUtils.isEmpty(data)) { 187 final String authority = host.toLowerCase(); 188 final Uri imUri = new Uri.Builder().scheme(Constants.SCHEME_IMTO).authority( 189 authority).appendPath(data).build(); 190 mIntent = new Intent(Intent.ACTION_SENDTO, imUri); 191 192 // If the address is also available for a video chat, we'll show the capability 193 // as a secondary action. 194 final Integer chatCapabilityObj = entryValues.getAsInteger(Im.CHAT_CAPABILITY); 195 final int chatCapability = chatCapabilityObj == null ? 0 : chatCapabilityObj; 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 = 215 entryValues.getAsString(StructuredPostal.FORMATTED_ADDRESS); 216 if (!TextUtils.isEmpty(postalAddress)) { 217 mIntent = StructuredPostalUtils.getViewPostalAddressIntent(postalAddress); 218 } 219 } 220 221 if (mIntent == null) { 222 // Otherwise fall back to default VIEW action 223 mIntent = new Intent(Intent.ACTION_VIEW); 224 mIntent.setDataAndType(mDataUri, mimeType); 225 } 226 227 mIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 228 } 229 230 @Override 231 public int getPresence() { 232 return mPresence; 233 } 234 235 public void setPresence(int presence) { 236 mPresence = presence; 237 } 238 239 private boolean isProtocolValid(ContentValues entryValues) { 240 final String protocol = entryValues.getAsString(Im.PROTOCOL); 241 if (protocol == null) { 242 return false; 243 } 244 try { 245 Integer.valueOf(protocol); 246 } catch (NumberFormatException e) { 247 return false; 248 } 249 return true; 250 } 251 252 @Override 253 public CharSequence getSubtitle() { 254 return mSubtitle; 255 } 256 257 @Override 258 public CharSequence getBody() { 259 return mBody; 260 } 261 262 @Override 263 public String getMimeType() { 264 return mMimeType; 265 } 266 267 @Override 268 public Uri getDataUri() { 269 return mDataUri; 270 } 271 272 @Override 273 public long getDataId() { 274 return mDataId; 275 } 276 277 @Override 278 public Boolean isPrimary() { 279 return mIsPrimary; 280 } 281 282 @Override 283 public Drawable getAlternateIcon() { 284 if (mAlternateIconRes == 0) return null; 285 286 final String resourcePackageName = mKind.resourcePackageName; 287 if (resourcePackageName == null) { 288 return mContext.getResources().getDrawable(mAlternateIconRes); 289 } 290 291 final PackageManager pm = mContext.getPackageManager(); 292 return pm.getDrawable(resourcePackageName, mAlternateIconRes, null); 293 } 294 295 @Override 296 public String getAlternateIconDescription() { 297 if (mAlternateIconDescriptionRes == 0) return null; 298 return mContext.getResources().getString(mAlternateIconDescriptionRes); 299 } 300 301 @Override 302 public Intent getIntent() { 303 return mIntent; 304 } 305 306 @Override 307 public Intent getAlternateIntent() { 308 return mAlternateIntent; 309 } 310 311 @Override 312 public boolean collapseWith(Action other) { 313 if (!shouldCollapseWith(other)) { 314 return false; 315 } 316 return true; 317 } 318 319 @Override 320 public boolean shouldCollapseWith(Action t) { 321 if (t == null) { 322 return false; 323 } 324 if (!(t instanceof DataAction)) { 325 Log.e(TAG, "t must be DataAction"); 326 return false; 327 } 328 DataAction that = (DataAction)t; 329 if (!ContactsUtils.shouldCollapse(mMimeType, mBody, that.mMimeType, that.mBody)) { 330 return false; 331 } 332 if (!TextUtils.equals(mMimeType, that.mMimeType) 333 || !ContactsUtils.areIntentActionEqual(mIntent, that.mIntent)) { 334 return false; 335 } 336 return true; 337 } 338 } 339