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