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 android.content.ComponentName; 20 import android.content.ContentUris; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.pm.PackageManager; 24 import android.graphics.drawable.Drawable; 25 import android.net.Uri; 26 import android.provider.ContactsContract.CommonDataKinds.Im; 27 import android.provider.ContactsContract.Data; 28 import android.telecom.PhoneAccount; 29 import android.text.TextUtils; 30 import android.util.Log; 31 32 import com.android.contacts.CallUtil; 33 import com.android.contacts.ContactsUtils; 34 import com.android.contacts.MoreContactUtils; 35 import com.android.contacts.R; 36 import com.android.contacts.model.account.AccountType.EditType; 37 import com.android.contacts.model.dataitem.DataItem; 38 import com.android.contacts.model.dataitem.DataKind; 39 import com.android.contacts.model.dataitem.EmailDataItem; 40 import com.android.contacts.model.dataitem.ImDataItem; 41 import com.android.contacts.model.dataitem.PhoneDataItem; 42 import com.android.contacts.model.dataitem.SipAddressDataItem; 43 import com.android.contacts.model.dataitem.StructuredPostalDataItem; 44 import com.android.contacts.model.dataitem.WebsiteDataItem; 45 import com.android.contacts.quickcontact.WebAddress.ParseException; 46 import com.android.contacts.util.PhoneCapabilityTester; 47 import com.android.contacts.util.StructuredPostalUtils; 48 49 /** 50 * Description of a specific {@link Data#_ID} item, with style information 51 * defined by a {@link DataKind}. 52 */ 53 public class DataAction implements Action { 54 private static final String TAG = "DataAction"; 55 56 private final Context mContext; 57 private final DataKind mKind; 58 private final String mMimeType; 59 private final Integer mTimesUsed; 60 private final Long mLastTimeUsed; 61 62 private CharSequence mBody; 63 private CharSequence mSubtitle; 64 private Intent mIntent; 65 private Intent mAlternateIntent; 66 private int mAlternateIconDescriptionRes; 67 private int mAlternateIconRes; 68 private int mPresence = -1; 69 70 private Uri mDataUri; 71 private long mDataId; 72 private boolean mIsPrimary; 73 private boolean mIsSuperPrimary; 74 75 /** 76 * Create an action from common {@link Data} elements. 77 */ 78 public DataAction(Context context, DataItem item, DataKind kind) { 79 mContext = context; 80 mKind = kind; 81 mMimeType = item.getMimeType(); 82 mTimesUsed = item.getTimesUsed(); 83 mLastTimeUsed = item.getLastTimeUsed(); 84 85 // Determine type for subtitle 86 mSubtitle = ""; 87 if (item.hasKindTypeColumn(kind)) { 88 final int typeValue = item.getKindTypeColumn(kind); 89 90 // get type string 91 for (EditType type : kind.typeList) { 92 if (type.rawValue == typeValue) { 93 if (type.customColumn == null) { 94 // Non-custom type. Get its description from the resource 95 mSubtitle = context.getString(type.labelRes); 96 } else { 97 // Custom type. Read it from the database 98 mSubtitle = item.getContentValues().getAsString(type.customColumn); 99 } 100 break; 101 } 102 } 103 } 104 105 mIsPrimary = item.isPrimary(); 106 mIsSuperPrimary = item.isSuperPrimary(); 107 mBody = item.buildDataStringForDisplay(context, kind); 108 109 mDataId = item.getId(); 110 mDataUri = ContentUris.withAppendedId(Data.CONTENT_URI, mDataId); 111 112 final boolean hasPhone = PhoneCapabilityTester.isPhone(mContext); 113 final ComponentName smsComponent = PhoneCapabilityTester.getSmsComponent(mContext); 114 final boolean hasSms = (smsComponent != null); 115 116 // Handle well-known MIME-types with special care 117 if (item instanceof PhoneDataItem) { 118 if (PhoneCapabilityTester.isPhone(mContext)) { 119 PhoneDataItem phone = (PhoneDataItem) item; 120 final String number = phone.getNumber(); 121 if (!TextUtils.isEmpty(number)) { 122 123 final Intent phoneIntent = hasPhone ? CallUtil.getCallIntent(number) 124 : null; 125 Intent smsIntent = null; 126 if (hasSms) { 127 smsIntent = new Intent(Intent.ACTION_SENDTO, 128 Uri.fromParts(ContactsUtils.SCHEME_SMSTO, number, null)); 129 smsIntent.setComponent(smsComponent); 130 } 131 132 // Configure Icons and Intents. Notice actionIcon is already set to the phone 133 if (hasPhone && hasSms) { 134 mIntent = phoneIntent; 135 mAlternateIntent = smsIntent; 136 mAlternateIconRes = kind.iconAltRes; 137 mAlternateIconDescriptionRes = kind.iconAltDescriptionRes; 138 } else if (hasPhone) { 139 mIntent = phoneIntent; 140 } else if (hasSms) { 141 mIntent = smsIntent; 142 } 143 } 144 } 145 } else if (item instanceof SipAddressDataItem) { 146 if (PhoneCapabilityTester.isSipPhone(mContext)) { 147 final SipAddressDataItem sip = (SipAddressDataItem) item; 148 final String address = sip.getSipAddress(); 149 if (!TextUtils.isEmpty(address)) { 150 final Uri callUri = Uri.fromParts(PhoneAccount.SCHEME_SIP, address, null); 151 mIntent = CallUtil.getCallIntent(callUri); 152 // Note that this item will get a SIP-specific variant 153 // of the "call phone" icon, rather than the standard 154 // app icon for the Phone app (which we show for 155 // regular phone numbers.) That's because the phone 156 // app explicitly specifies an android:icon attribute 157 // for the SIP-related intent-filters in its manifest. 158 } 159 } 160 } else if (item instanceof EmailDataItem) { 161 final EmailDataItem email = (EmailDataItem) item; 162 final String address = email.getData(); 163 if (!TextUtils.isEmpty(address)) { 164 final Uri mailUri = Uri.fromParts(ContactsUtils.SCHEME_MAILTO, address, null); 165 mIntent = new Intent(Intent.ACTION_SENDTO, mailUri); 166 } 167 168 } else if (item instanceof WebsiteDataItem) { 169 final WebsiteDataItem website = (WebsiteDataItem) item; 170 final String url = website.getUrl(); 171 if (!TextUtils.isEmpty(url)) { 172 try { 173 final WebAddress webAddress = new WebAddress(url); 174 mIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(webAddress.toString())); 175 } catch (ParseException e) { 176 mIntent = null; 177 } 178 } 179 180 } else if (item instanceof ImDataItem) { 181 ImDataItem im = (ImDataItem) item; 182 final boolean isEmail = im.isCreatedFromEmail(); 183 if (isEmail || im.isProtocolValid()) { 184 final int protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK : im.getProtocol(); 185 186 if (isEmail) { 187 // Use Google Talk string when using Email, and clear data 188 // Uri so we don't try saving Email as primary. 189 mSubtitle = Im.getProtocolLabel(context.getResources(), Im.PROTOCOL_GOOGLE_TALK, 190 null); 191 mDataUri = null; 192 } 193 194 String host = im.getCustomProtocol(); 195 String data = im.getData(); 196 if (protocol != Im.PROTOCOL_CUSTOM) { 197 // Try bringing in a well-known host for specific protocols 198 host = ContactsUtils.lookupProviderNameFromId(protocol); 199 } 200 201 if (!TextUtils.isEmpty(host) && !TextUtils.isEmpty(data)) { 202 final String authority = host.toLowerCase(); 203 final Uri imUri = new Uri.Builder().scheme(ContactsUtils.SCHEME_IMTO).authority( 204 authority).appendPath(data).build(); 205 mIntent = new Intent(Intent.ACTION_SENDTO, imUri); 206 207 // If the address is also available for a video chat, we'll show the capability 208 // as a secondary action. 209 final int chatCapability = im.getChatCapability(); 210 final boolean isVideoChatCapable = 211 (chatCapability & Im.CAPABILITY_HAS_CAMERA) != 0; 212 final boolean isAudioChatCapable = 213 (chatCapability & Im.CAPABILITY_HAS_VOICE) != 0; 214 if (isVideoChatCapable || isAudioChatCapable) { 215 mAlternateIntent = new Intent( 216 Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?call")); 217 if (isVideoChatCapable) { 218 mAlternateIconRes = R.drawable.quantum_ic_videocam_vd_theme_24; 219 mAlternateIconDescriptionRes = R.string.video_chat; 220 } else { 221 mAlternateIconRes = R.drawable.quantum_ic_mic_vd_theme_24; 222 mAlternateIconDescriptionRes = R.string.audio_chat; 223 } 224 } 225 } 226 } 227 } else if (item instanceof StructuredPostalDataItem) { 228 StructuredPostalDataItem postal = (StructuredPostalDataItem) item; 229 final String postalAddress = postal.getFormattedAddress(); 230 if (!TextUtils.isEmpty(postalAddress)) { 231 mIntent = StructuredPostalUtils.getViewPostalAddressIntent(postalAddress); 232 } 233 } 234 235 if (mIntent == null) { 236 // Otherwise fall back to default VIEW action 237 mIntent = new Intent(Intent.ACTION_VIEW); 238 mIntent.setDataAndType(mDataUri, item.getMimeType()); 239 } 240 241 mIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 242 } 243 244 @Override 245 public int getPresence() { 246 return mPresence; 247 } 248 249 public void setPresence(int presence) { 250 mPresence = presence; 251 } 252 253 @Override 254 public CharSequence getSubtitle() { 255 return mSubtitle; 256 } 257 258 @Override 259 public CharSequence getBody() { 260 return mBody; 261 } 262 263 @Override 264 public String getMimeType() { 265 return mMimeType; 266 } 267 268 @Override 269 public Uri getDataUri() { 270 return mDataUri; 271 } 272 273 @Override 274 public long getDataId() { 275 return mDataId; 276 } 277 278 @Override 279 public boolean isPrimary() { 280 return mIsPrimary; 281 } 282 283 @Override 284 public boolean isSuperPrimary() { 285 return mIsSuperPrimary; 286 } 287 288 @Override 289 public Drawable getAlternateIcon() { 290 if (mAlternateIconRes == 0) return null; 291 292 final String resourcePackageName = mKind.resourcePackageName; 293 if (resourcePackageName == null) { 294 return mContext.getResources().getDrawable(mAlternateIconRes); 295 } 296 297 final PackageManager pm = mContext.getPackageManager(); 298 return pm.getDrawable(resourcePackageName, mAlternateIconRes, null); 299 } 300 301 @Override 302 public String getAlternateIconDescription() { 303 if (mAlternateIconDescriptionRes == 0) return null; 304 return mContext.getResources().getString(mAlternateIconDescriptionRes); 305 } 306 307 @Override 308 public Intent getIntent() { 309 return mIntent; 310 } 311 312 @Override 313 public Intent getAlternateIntent() { 314 return mAlternateIntent; 315 } 316 317 @Override 318 public void collapseWith(Action other) { 319 // No-op 320 } 321 322 @Override 323 public boolean shouldCollapseWith(Action t, Context context) { 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 (!MoreContactUtils.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 @Override 343 public Integer getTimesUsed() { 344 return mTimesUsed; 345 } 346 347 @Override 348 public Long getLastTimeUsed() { 349 return mLastTimeUsed; 350 } 351 } 352