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