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.R;
33 import com.android.contacts.common.CallUtil;
34 import com.android.contacts.common.ContactsUtils;
35 import com.android.contacts.common.MoreContactUtils;
36 import com.android.contacts.common.model.account.AccountType.EditType;
37 import com.android.contacts.common.model.dataitem.DataItem;
38 import com.android.contacts.common.model.dataitem.DataKind;
39 import com.android.contacts.common.model.dataitem.EmailDataItem;
40 import com.android.contacts.common.model.dataitem.ImDataItem;
41 import com.android.contacts.common.model.dataitem.PhoneDataItem;
42 import com.android.contacts.common.model.dataitem.SipAddressDataItem;
43 import com.android.contacts.common.model.dataitem.StructuredPostalDataItem;
44 import com.android.contacts.common.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      */
DataAction(Context context, DataItem item, DataKind kind)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.sym_action_videochat_holo_light;
219                             mAlternateIconDescriptionRes = R.string.video_chat;
220                         } else {
221                             mAlternateIconRes = R.drawable.sym_action_audiochat_holo_light;
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
getPresence()245     public int getPresence() {
246         return mPresence;
247     }
248 
setPresence(int presence)249     public void setPresence(int presence) {
250         mPresence = presence;
251     }
252 
253     @Override
getSubtitle()254     public CharSequence getSubtitle() {
255         return mSubtitle;
256     }
257 
258     @Override
getBody()259     public CharSequence getBody() {
260         return mBody;
261     }
262 
263     @Override
getMimeType()264     public String getMimeType() {
265         return mMimeType;
266     }
267 
268     @Override
getDataUri()269     public Uri getDataUri() {
270         return mDataUri;
271     }
272 
273     @Override
getDataId()274     public long getDataId() {
275         return mDataId;
276     }
277 
278     @Override
isPrimary()279     public boolean isPrimary() {
280         return mIsPrimary;
281     }
282 
283     @Override
isSuperPrimary()284     public boolean isSuperPrimary() {
285         return mIsSuperPrimary;
286     }
287 
288     @Override
getAlternateIcon()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
getAlternateIconDescription()302     public String getAlternateIconDescription() {
303         if (mAlternateIconDescriptionRes == 0) return null;
304         return mContext.getResources().getString(mAlternateIconDescriptionRes);
305     }
306 
307     @Override
getIntent()308     public Intent getIntent() {
309         return mIntent;
310     }
311 
312     @Override
getAlternateIntent()313     public Intent getAlternateIntent() {
314         return mAlternateIntent;
315     }
316 
317     @Override
collapseWith(Action other)318     public void collapseWith(Action other) {
319         // No-op
320     }
321 
322     @Override
shouldCollapseWith(Action t, Context context)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
getTimesUsed()343     public Integer getTimesUsed() {
344         return mTimesUsed;
345     }
346 
347     @Override
getLastTimeUsed()348     public Long getLastTimeUsed() {
349         return mLastTimeUsed;
350     }
351 }
352