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