1 /* 2 * Copyright (C) 2015 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 package com.android.messaging.datamodel.media; 17 18 import android.content.Intent; 19 import android.content.res.Resources; 20 import android.content.res.Resources.NotFoundException; 21 import android.net.Uri; 22 import android.provider.ContactsContract.CommonDataKinds.Im; 23 import android.provider.ContactsContract.CommonDataKinds.Organization; 24 import android.provider.ContactsContract.CommonDataKinds.Phone; 25 import androidx.collection.ArrayMap; 26 import android.text.TextUtils; 27 28 import com.android.messaging.Factory; 29 import com.android.messaging.R; 30 import com.android.messaging.datamodel.MediaScratchFileProvider; 31 import com.android.messaging.datamodel.data.PersonItemData; 32 import com.android.messaging.util.ContactUtil; 33 import com.android.messaging.util.LogUtil; 34 import com.android.messaging.util.SafeAsyncTask; 35 import com.android.vcard.VCardEntry; 36 import com.android.vcard.VCardEntry.EmailData; 37 import com.android.vcard.VCardEntry.ImData; 38 import com.android.vcard.VCardEntry.NoteData; 39 import com.android.vcard.VCardEntry.OrganizationData; 40 import com.android.vcard.VCardEntry.PhoneData; 41 import com.android.vcard.VCardEntry.PostalData; 42 import com.android.vcard.VCardEntry.WebsiteData; 43 import com.android.vcard.VCardProperty; 44 45 import java.io.UnsupportedEncodingException; 46 import java.net.URLEncoder; 47 import java.util.ArrayList; 48 import java.util.List; 49 50 /** 51 * Holds one entry item (i.e. a single contact) within a VCard resource. It is able to take 52 * a VCardEntry and extract relevant information from it. 53 */ 54 public class VCardResourceEntry { 55 public static final String PROPERTY_KIND = "KIND"; 56 57 public static final String KIND_LOCATION = "location"; 58 59 private final List<VCardResourceEntry.VCardResourceEntryDestinationItem> mContactInfo; 60 private final Uri mAvatarUri; 61 private final String mDisplayName; 62 private final CustomVCardEntry mVCard; 63 VCardResourceEntry(final CustomVCardEntry vcard, final Uri avatarUri)64 public VCardResourceEntry(final CustomVCardEntry vcard, final Uri avatarUri) { 65 mContactInfo = getContactInfoFromVCardEntry(vcard); 66 mDisplayName = getDisplayNameFromVCardEntry(vcard); 67 mAvatarUri = avatarUri; 68 mVCard = vcard; 69 } 70 close()71 void close() { 72 // If the avatar image was temporarily saved in the scratch folder, remove that. 73 if (MediaScratchFileProvider.isMediaScratchSpaceUri(mAvatarUri)) { 74 SafeAsyncTask.executeOnThreadPool(new Runnable() { 75 @Override 76 public void run() { 77 Factory.get().getApplicationContext().getContentResolver().delete( 78 mAvatarUri, null, null); 79 } 80 }); 81 } 82 } 83 getKind()84 public String getKind() { 85 VCardProperty kindProperty = mVCard.getProperty(PROPERTY_KIND); 86 return kindProperty == null ? null : kindProperty.getRawValue(); 87 } 88 getAvatarUri()89 public Uri getAvatarUri() { 90 return mAvatarUri; 91 } 92 getDisplayName()93 public String getDisplayName() { 94 return mDisplayName; 95 } 96 getDisplayAddress()97 public String getDisplayAddress() { 98 List<PostalData> postalList = mVCard.getPostalList(); 99 if (postalList == null || postalList.size() < 1) { 100 return null; 101 } 102 103 return formatAddress(postalList.get(0)); 104 } 105 getNotes()106 public String getNotes() { 107 List<NoteData> notes = mVCard.getNotes(); 108 if (notes == null || notes.size() == 0) { 109 return null; 110 } 111 StringBuilder noteBuilder = new StringBuilder(); 112 for (NoteData note : notes) { 113 noteBuilder.append(note.getNote()); 114 } 115 return noteBuilder.toString(); 116 } 117 118 /** 119 * Returns a UI-facing representation that can be bound and consumed by the UI layer to display 120 * this VCard resource entry. 121 */ getDisplayItem()122 public PersonItemData getDisplayItem() { 123 return new PersonItemData() { 124 @Override 125 public Uri getAvatarUri() { 126 return VCardResourceEntry.this.getAvatarUri(); 127 } 128 129 @Override 130 public String getDisplayName() { 131 return VCardResourceEntry.this.getDisplayName(); 132 } 133 134 @Override 135 public String getDetails() { 136 return null; 137 } 138 139 @Override 140 public Intent getClickIntent() { 141 return null; 142 } 143 144 @Override 145 public long getContactId() { 146 return ContactUtil.INVALID_CONTACT_ID; 147 } 148 149 @Override 150 public String getLookupKey() { 151 return null; 152 } 153 154 @Override 155 public String getNormalizedDestination() { 156 return null; 157 } 158 }; 159 } 160 161 public List<VCardResourceEntry.VCardResourceEntryDestinationItem> getContactInfo() { 162 return mContactInfo; 163 } 164 165 private static List<VCardResourceEntryDestinationItem> getContactInfoFromVCardEntry( 166 final VCardEntry vcard) { 167 final Resources resources = Factory.get().getApplicationContext().getResources(); 168 final List<VCardResourceEntry.VCardResourceEntryDestinationItem> retList = 169 new ArrayList<VCardResourceEntry.VCardResourceEntryDestinationItem>(); 170 if (vcard.getPhoneList() != null) { 171 for (final PhoneData phone : vcard.getPhoneList()) { 172 final Intent intent = new Intent(Intent.ACTION_DIAL); 173 intent.setData(Uri.parse("tel:" + phone.getNumber())); 174 retList.add(new VCardResourceEntryDestinationItem(phone.getNumber(), 175 Phone.getTypeLabel(resources, phone.getType(), phone.getLabel()).toString(), 176 intent)); 177 } 178 } 179 180 if (vcard.getEmailList() != null) { 181 for (final EmailData email : vcard.getEmailList()) { 182 final Intent intent = new Intent(Intent.ACTION_SENDTO); 183 intent.setData(Uri.parse("mailto:")); 184 intent.putExtra(Intent.EXTRA_EMAIL, new String[] { email.getAddress() }); 185 retList.add(new VCardResourceEntryDestinationItem(email.getAddress(), 186 Phone.getTypeLabel(resources, email.getType(), 187 email.getLabel()).toString(), intent)); 188 } 189 } 190 191 if (vcard.getPostalList() != null) { 192 for (final PostalData postalData : vcard.getPostalList()) { 193 String type; 194 try { 195 type = resources. 196 getStringArray(android.R.array.postalAddressTypes) 197 [postalData.getType() - 1]; 198 } catch (final NotFoundException ex) { 199 type = resources.getStringArray(android.R.array.postalAddressTypes)[2]; 200 } catch (final Exception e) { 201 LogUtil.e(LogUtil.BUGLE_TAG, "createContactItem postal Exception:" + e); 202 type = resources.getStringArray(android.R.array.postalAddressTypes)[2]; 203 } 204 Intent intent = new Intent(Intent.ACTION_VIEW); 205 final String address = formatAddress(postalData); 206 try { 207 intent.setData(Uri.parse("geo:0,0?q=" + URLEncoder.encode(address, "UTF-8"))); 208 } catch (UnsupportedEncodingException e) { 209 intent = null; 210 } 211 212 retList.add(new VCardResourceEntryDestinationItem(address, type, intent)); 213 } 214 } 215 216 if (vcard.getImList() != null) { 217 for (final ImData imData : vcard.getImList()) { 218 String type = null; 219 try { 220 type = resources. 221 getString(Im.getProtocolLabelResource(imData.getProtocol())); 222 } catch (final NotFoundException ex) { 223 // Do nothing since this implies an empty label. 224 } 225 retList.add(new VCardResourceEntryDestinationItem(imData.getAddress(), type, null)); 226 } 227 } 228 229 if (vcard.getOrganizationList() != null) { 230 for (final OrganizationData organtization : vcard.getOrganizationList()) { 231 String type = null; 232 try { 233 type = resources.getString(Organization.getTypeLabelResource( 234 organtization.getType())); 235 } catch (final NotFoundException ex) { 236 //set other kind as "other" 237 type = resources.getStringArray(android.R.array.organizationTypes)[1]; 238 } catch (final Exception e) { 239 LogUtil.e(LogUtil.BUGLE_TAG, "createContactItem org Exception:" + e); 240 type = resources.getStringArray(android.R.array.organizationTypes)[1]; 241 } 242 retList.add(new VCardResourceEntryDestinationItem( 243 organtization.getOrganizationName(), type, null)); 244 } 245 } 246 247 if (vcard.getWebsiteList() != null) { 248 for (final WebsiteData web : vcard.getWebsiteList()) { 249 if (web != null && TextUtils.isGraphic(web.getWebsite())){ 250 String website = web.getWebsite(); 251 if (!website.startsWith("http://") && !website.startsWith("https://")) { 252 // Prefix required for parsing to end up with a scheme and result in 253 // navigation 254 website = "http://" + website; 255 } 256 final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(website)); 257 retList.add(new VCardResourceEntryDestinationItem(web.getWebsite(), null, 258 intent)); 259 } 260 } 261 } 262 263 if (vcard.getBirthday() != null) { 264 final String birthday = vcard.getBirthday(); 265 if (TextUtils.isGraphic(birthday)){ 266 retList.add(new VCardResourceEntryDestinationItem(birthday, 267 resources.getString(R.string.vcard_detail_birthday_label), null)); 268 } 269 } 270 271 if (vcard.getNotes() != null) { 272 for (final NoteData note : vcard.getNotes()) { 273 final ArrayMap<String, String> curChildMap = new ArrayMap<String, String>(); 274 if (TextUtils.isGraphic(note.getNote())){ 275 retList.add(new VCardResourceEntryDestinationItem(note.getNote(), 276 resources.getString(R.string.vcard_detail_notes_label), null)); 277 } 278 } 279 } 280 return retList; 281 } 282 283 private static String formatAddress(final PostalData postalData) { 284 final StringBuilder sb = new StringBuilder(); 285 final String poBox = postalData.getPobox(); 286 if (!TextUtils.isEmpty(poBox)) { 287 sb.append(poBox); 288 sb.append(" "); 289 } 290 final String extendedAddress = postalData.getExtendedAddress(); 291 if (!TextUtils.isEmpty(extendedAddress)) { 292 sb.append(extendedAddress); 293 sb.append(" "); 294 } 295 final String street = postalData.getStreet(); 296 if (!TextUtils.isEmpty(street)) { 297 sb.append(street); 298 sb.append(" "); 299 } 300 final String localty = postalData.getLocalty(); 301 if (!TextUtils.isEmpty(localty)) { 302 sb.append(localty); 303 sb.append(" "); 304 } 305 final String region = postalData.getRegion(); 306 if (!TextUtils.isEmpty(region)) { 307 sb.append(region); 308 sb.append(" "); 309 } 310 final String postalCode = postalData.getPostalCode(); 311 if (!TextUtils.isEmpty(postalCode)) { 312 sb.append(postalCode); 313 sb.append(" "); 314 } 315 final String country = postalData.getCountry(); 316 if (!TextUtils.isEmpty(country)) { 317 sb.append(country); 318 } 319 return sb.toString(); 320 } 321 322 private static String getDisplayNameFromVCardEntry(final VCardEntry vcard) { 323 String name = vcard.getDisplayName(); 324 if (name == null) { 325 vcard.consolidateFields(); 326 name = vcard.getDisplayName(); 327 } 328 return name; 329 } 330 331 /** 332 * Represents one entry line (e.g. phone number and phone label) for a single contact. Each 333 * VCardResourceEntry may hold one or more VCardResourceEntryDestinationItem's. 334 */ 335 public static class VCardResourceEntryDestinationItem { 336 private final String mDisplayDestination; 337 private final String mDestinationType; 338 private final Intent mClickIntent; 339 public VCardResourceEntryDestinationItem(final String displayDestination, 340 final String destinationType, final Intent clickIntent) { 341 mDisplayDestination = displayDestination; 342 mDestinationType = destinationType; 343 mClickIntent = clickIntent; 344 } 345 346 /** 347 * Returns a UI-facing representation that can be bound and consumed by the UI layer to 348 * display this VCard resource destination entry. 349 */ 350 public PersonItemData getDisplayItem() { 351 return new PersonItemData() { 352 @Override 353 public Uri getAvatarUri() { 354 return null; 355 } 356 357 @Override 358 public String getDisplayName() { 359 return mDisplayDestination; 360 } 361 362 @Override 363 public String getDetails() { 364 return mDestinationType; 365 } 366 367 @Override 368 public Intent getClickIntent() { 369 return mClickIntent; 370 } 371 372 @Override 373 public long getContactId() { 374 return ContactUtil.INVALID_CONTACT_ID; 375 } 376 377 @Override 378 public String getLookupKey() { 379 return null; 380 } 381 382 @Override 383 public String getNormalizedDestination() { 384 return null; 385 } 386 }; 387 } 388 } 389 } 390