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