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