1/*
2 * Copyright (C) 2012 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.common.model.dataitem;
18
19import android.content.ContentValues;
20import android.content.Context;
21import android.provider.ContactsContract.CommonDataKinds.Email;
22import android.provider.ContactsContract.CommonDataKinds.Event;
23import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
24import android.provider.ContactsContract.CommonDataKinds.Identity;
25import android.provider.ContactsContract.CommonDataKinds.Im;
26import android.provider.ContactsContract.CommonDataKinds.Nickname;
27import android.provider.ContactsContract.CommonDataKinds.Note;
28import android.provider.ContactsContract.CommonDataKinds.Organization;
29import android.provider.ContactsContract.CommonDataKinds.Phone;
30import android.provider.ContactsContract.CommonDataKinds.Photo;
31import android.provider.ContactsContract.CommonDataKinds.Relation;
32import android.provider.ContactsContract.CommonDataKinds.SipAddress;
33import android.provider.ContactsContract.CommonDataKinds.StructuredName;
34import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
35import android.provider.ContactsContract.CommonDataKinds.Website;
36import android.provider.ContactsContract.Contacts.Data;
37import android.provider.ContactsContract.Contacts.Entity;
38
39import com.android.contacts.common.Collapser;
40import com.android.contacts.common.MoreContactUtils;
41import com.android.contacts.common.model.RawContactModifier;
42
43/**
44 * This is the base class for data items, which represents a row from the Data table.
45 */
46public class DataItem implements Collapser.Collapsible<DataItem> {
47
48    private final ContentValues mContentValues;
49    protected DataKind mKind;
50
51    protected DataItem(ContentValues values) {
52        mContentValues = values;
53    }
54
55    /**
56     * Factory for creating subclasses of DataItem objects based on the mimetype in the
57     * content values.  Raw contact is the raw contact that this data item is associated with.
58     */
59    public static DataItem createFrom(ContentValues values) {
60        final String mimeType = values.getAsString(Data.MIMETYPE);
61        if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
62            return new GroupMembershipDataItem(values);
63        } else if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
64            return new StructuredNameDataItem(values);
65        } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
66            return new PhoneDataItem(values);
67        } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
68            return new EmailDataItem(values);
69        } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType)) {
70            return new StructuredPostalDataItem(values);
71        } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType)) {
72            return new ImDataItem(values);
73        } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
74            return new OrganizationDataItem(values);
75        } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
76            return new NicknameDataItem(values);
77        } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType)) {
78            return new NoteDataItem(values);
79        } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType)) {
80            return new WebsiteDataItem(values);
81        } else if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType)) {
82            return new SipAddressDataItem(values);
83        } else if (Event.CONTENT_ITEM_TYPE.equals(mimeType)) {
84            return new EventDataItem(values);
85        } else if (Relation.CONTENT_ITEM_TYPE.equals(mimeType)) {
86            return new RelationDataItem(values);
87        } else if (Identity.CONTENT_ITEM_TYPE.equals(mimeType)) {
88            return new IdentityDataItem(values);
89        } else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
90            return new PhotoDataItem(values);
91        }
92
93        // generic
94        return new DataItem(values);
95    }
96
97    public ContentValues getContentValues() {
98        return mContentValues;
99    }
100
101    public void setRawContactId(long rawContactId) {
102        mContentValues.put(Data.RAW_CONTACT_ID, rawContactId);
103    }
104
105    public Long getRawContactId() {
106        return mContentValues.getAsLong(Data.RAW_CONTACT_ID);
107    }
108
109    /**
110     * Returns the data id.
111     */
112    public long getId() {
113        return mContentValues.getAsLong(Data._ID);
114    }
115
116    /**
117     * Returns the mimetype of the data.
118     */
119    public String getMimeType() {
120        return mContentValues.getAsString(Data.MIMETYPE);
121    }
122
123    public void setMimeType(String mimeType) {
124        mContentValues.put(Data.MIMETYPE, mimeType);
125    }
126
127    public boolean isPrimary() {
128        Integer primary = mContentValues.getAsInteger(Data.IS_PRIMARY);
129        return primary != null && primary != 0;
130    }
131
132    public boolean isSuperPrimary() {
133        Integer superPrimary = mContentValues.getAsInteger(Data.IS_SUPER_PRIMARY);
134        return superPrimary != null && superPrimary != 0;
135    }
136
137    public boolean hasKindTypeColumn(DataKind kind) {
138        final String key = kind.typeColumn;
139        return key != null && mContentValues.containsKey(key) &&
140            mContentValues.getAsInteger(key) != null;
141    }
142
143    public int getKindTypeColumn(DataKind kind) {
144        final String key = kind.typeColumn;
145        return mContentValues.getAsInteger(key);
146    }
147
148    /**
149     * Indicates the carrier presence value for the current {@link DataItem}.
150     *
151     * @return {@link Data#CARRIER_PRESENCE_VT_CAPABLE} if the {@link DataItem} supports carrier
152     *      video calling, {@code 0} otherwise.
153     */
154    public int getCarrierPresence() {
155        return mContentValues.getAsInteger(Data.CARRIER_PRESENCE);
156    }
157
158    /**
159     * This builds the data string depending on the type of data item by using the generic
160     * DataKind object underneath.
161     */
162    public String buildDataString(Context context, DataKind kind) {
163        if (kind.actionBody == null) {
164            return null;
165        }
166        CharSequence actionBody = kind.actionBody.inflateUsing(context, mContentValues);
167        return actionBody == null ? null : actionBody.toString();
168    }
169
170    /**
171     * This builds the data string(intended for display) depending on the type of data item. It
172     * returns the same value as {@link #buildDataString} by default, but certain data items can
173     * override it to provide their version of formatted data strings.
174     *
175     * @return Data string representing the data item, possibly formatted for display
176     */
177    public String buildDataStringForDisplay(Context context, DataKind kind) {
178        return buildDataString(context, kind);
179    }
180
181    public void setDataKind(DataKind kind) {
182        mKind = kind;
183    }
184
185    public DataKind getDataKind() {
186        return mKind;
187    }
188
189    public Integer getTimesUsed() {
190        return mContentValues.getAsInteger(Entity.TIMES_USED);
191    }
192
193    public Long getLastTimeUsed() {
194        return mContentValues.getAsLong(Entity.LAST_TIME_USED);
195    }
196
197    @Override
198    public void collapseWith(DataItem that) {
199        DataKind thisKind = getDataKind();
200        DataKind thatKind = that.getDataKind();
201        // If this does not have a type and that does, or if that's type is higher precedence,
202        // use that's type
203        if ((!hasKindTypeColumn(thisKind) && that.hasKindTypeColumn(thatKind)) ||
204                that.hasKindTypeColumn(thatKind) &&
205                RawContactModifier.getTypePrecedence(thisKind, getKindTypeColumn(thisKind))
206                >
207                RawContactModifier.getTypePrecedence(thatKind, that.getKindTypeColumn(thatKind))) {
208            mContentValues.put(thatKind.typeColumn, that.getKindTypeColumn(thatKind));
209            mKind = thatKind;
210        }
211
212        // Choose the max of the maxLines and maxLabelLines values.
213        mKind.maxLinesForDisplay = Math.max(thisKind.maxLinesForDisplay,
214                thatKind.maxLinesForDisplay);
215
216        // If any of the collapsed entries are super primary make the whole thing super primary.
217        if (isSuperPrimary() || that.isSuperPrimary()) {
218            mContentValues.put(Data.IS_SUPER_PRIMARY, 1);
219            mContentValues.put(Data.IS_PRIMARY, 1);
220        }
221
222        // If any of the collapsed entries are primary make the whole thing primary.
223        if (isPrimary() || that.isPrimary()) {
224            mContentValues.put(Data.IS_PRIMARY, 1);
225        }
226
227        // Add up the times used
228        mContentValues.put(Entity.TIMES_USED, (getTimesUsed() == null ? 0 : getTimesUsed()) +
229                (that.getTimesUsed() == null ? 0 : that.getTimesUsed()));
230
231        // Use the most recent time
232        mContentValues.put(Entity.LAST_TIME_USED,
233                Math.max(getLastTimeUsed() == null ? 0 : getLastTimeUsed(),
234                        that.getLastTimeUsed() == null ? 0 : that.getLastTimeUsed()));
235    }
236
237    @Override
238    public boolean shouldCollapseWith(DataItem t, Context context) {
239        if (mKind == null || t.getDataKind() == null) {
240            return false;
241        }
242        return MoreContactUtils.shouldCollapse(getMimeType(), buildDataString(context, mKind),
243                t.getMimeType(), t.buildDataString(context, t.getDataKind()));
244    }
245}
246