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;
18 
19 import android.content.ContentValues;
20 import android.content.Context;
21 import android.content.Entity;
22 import android.net.Uri;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.provider.ContactsContract.Contacts;
26 import android.provider.ContactsContract.Data;
27 import android.provider.ContactsContract.RawContacts;
28 
29 import com.android.contacts.common.model.AccountTypeManager;
30 import com.android.contacts.common.model.account.AccountType;
31 import com.android.contacts.common.model.account.AccountWithDataSet;
32 import com.android.contacts.common.model.dataitem.DataItem;
33 import com.google.common.base.Objects;
34 import com.google.common.collect.Lists;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 /**
40  * RawContact represents a single raw contact in the raw contacts database.
41  * It has specialized getters/setters for raw contact
42  * items, and also contains a collection of DataItem objects.  A RawContact contains the information
43  * from a single account.
44  *
45  * This allows RawContact objects to be thought of as a class with raw contact
46  * fields (like account type, name, data set, sync state, etc.) and a list of
47  * DataItem objects that represent contact information elements (like phone
48  * numbers, email, address, etc.).
49  */
50 final public class RawContact implements Parcelable {
51 
52     private AccountTypeManager mAccountTypeManager;
53     private final ContentValues mValues;
54     private final ArrayList<NamedDataItem> mDataItems;
55 
56     final public static class NamedDataItem implements Parcelable {
57         public final Uri mUri;
58 
59         // This use to be a DataItem. DataItem creation is now delayed until the point of request
60         // since there is no benefit to storing them here due to the multiple inheritance.
61         // Eventually instanceof still has to be used anyways to determine which sub-class of
62         // DataItem it is. And having parent DataItem's here makes it very difficult to serialize or
63         // parcelable.
64         //
65         // Instead of having a common DataItem super class, we should refactor this to be a generic
66         // Object where the object is a concrete class that no longer relies on ContentValues.
67         // (this will also make the classes easier to use).
68         // Since instanceof is used later anyways, having a list of Objects won't hurt and is no
69         // worse than having a DataItem.
70         public final ContentValues mContentValues;
71 
NamedDataItem(Uri uri, ContentValues values)72         public NamedDataItem(Uri uri, ContentValues values) {
73             this.mUri = uri;
74             this.mContentValues = values;
75         }
76 
NamedDataItem(Parcel parcel)77         public NamedDataItem(Parcel parcel) {
78             this.mUri = parcel.readParcelable(Uri.class.getClassLoader());
79             this.mContentValues = parcel.readParcelable(ContentValues.class.getClassLoader());
80         }
81 
82         @Override
describeContents()83         public int describeContents() {
84             return 0;
85         }
86 
87         @Override
writeToParcel(Parcel parcel, int i)88         public void writeToParcel(Parcel parcel, int i) {
89             parcel.writeParcelable(mUri, i);
90             parcel.writeParcelable(mContentValues, i);
91         }
92 
93         public static final Parcelable.Creator<NamedDataItem> CREATOR
94                 = new Parcelable.Creator<NamedDataItem>() {
95 
96             @Override
97             public NamedDataItem createFromParcel(Parcel parcel) {
98                 return new NamedDataItem(parcel);
99             }
100 
101             @Override
102             public NamedDataItem[] newArray(int i) {
103                 return new NamedDataItem[i];
104             }
105         };
106 
107         @Override
hashCode()108         public int hashCode() {
109             return Objects.hashCode(mUri, mContentValues);
110         }
111 
112         @Override
equals(Object obj)113         public boolean equals(Object obj) {
114             if (obj == null) return false;
115             if (getClass() != obj.getClass()) return false;
116 
117             final NamedDataItem other = (NamedDataItem) obj;
118             return Objects.equal(mUri, other.mUri) &&
119                     Objects.equal(mContentValues, other.mContentValues);
120         }
121     }
122 
createFrom(Entity entity)123     public static RawContact createFrom(Entity entity) {
124         final ContentValues values = entity.getEntityValues();
125         final ArrayList<Entity.NamedContentValues> subValues = entity.getSubValues();
126 
127         RawContact rawContact = new RawContact(values);
128         for (Entity.NamedContentValues subValue : subValues) {
129             rawContact.addNamedDataItemValues(subValue.uri, subValue.values);
130         }
131         return rawContact;
132     }
133 
134     /**
135      * A RawContact object can be created with or without a context.
136      */
RawContact()137     public RawContact() {
138         this(new ContentValues());
139     }
140 
RawContact(ContentValues values)141     public RawContact(ContentValues values) {
142         mValues = values;
143         mDataItems = new ArrayList<NamedDataItem>();
144     }
145 
146     /**
147      * Constructor for the parcelable.
148      *
149      * @param parcel The parcel to de-serialize from.
150      */
RawContact(Parcel parcel)151     private RawContact(Parcel parcel) {
152         mValues = parcel.readParcelable(ContentValues.class.getClassLoader());
153         mDataItems = Lists.newArrayList();
154         parcel.readTypedList(mDataItems, NamedDataItem.CREATOR);
155     }
156 
157     @Override
describeContents()158     public int describeContents() {
159         return 0;
160     }
161 
162     @Override
writeToParcel(Parcel parcel, int i)163     public void writeToParcel(Parcel parcel, int i) {
164         parcel.writeParcelable(mValues, i);
165         parcel.writeTypedList(mDataItems);
166     }
167 
168     /**
169      * Create for building the parcelable.
170      */
171     public static final Parcelable.Creator<RawContact> CREATOR
172             = new Parcelable.Creator<RawContact>() {
173 
174         @Override
175         public RawContact createFromParcel(Parcel parcel) {
176             return new RawContact(parcel);
177         }
178 
179         @Override
180         public RawContact[] newArray(int i) {
181             return new RawContact[i];
182         }
183     };
184 
getAccountTypeManager(Context context)185     public AccountTypeManager getAccountTypeManager(Context context) {
186         if (mAccountTypeManager == null) {
187             mAccountTypeManager = AccountTypeManager.getInstance(context);
188         }
189         return mAccountTypeManager;
190     }
191 
getValues()192     public ContentValues getValues() {
193         return mValues;
194     }
195 
196     /**
197      * Returns the id of the raw contact.
198      */
getId()199     public Long getId() {
200         return getValues().getAsLong(RawContacts._ID);
201     }
202 
203     /**
204      * Returns the account name of the raw contact.
205      */
getAccountName()206     public String getAccountName() {
207         return getValues().getAsString(RawContacts.ACCOUNT_NAME);
208     }
209 
210     /**
211      * Returns the account type of the raw contact.
212      */
getAccountTypeString()213     public String getAccountTypeString() {
214         return getValues().getAsString(RawContacts.ACCOUNT_TYPE);
215     }
216 
217     /**
218      * Returns the data set of the raw contact.
219      */
getDataSet()220     public String getDataSet() {
221         return getValues().getAsString(RawContacts.DATA_SET);
222     }
223 
isDirty()224     public boolean isDirty() {
225         return getValues().getAsBoolean(RawContacts.DIRTY);
226     }
227 
getSourceId()228     public String getSourceId() {
229         return getValues().getAsString(RawContacts.SOURCE_ID);
230     }
231 
getSync1()232     public String getSync1() {
233         return getValues().getAsString(RawContacts.SYNC1);
234     }
235 
getSync2()236     public String getSync2() {
237         return getValues().getAsString(RawContacts.SYNC2);
238     }
239 
getSync3()240     public String getSync3() {
241         return getValues().getAsString(RawContacts.SYNC3);
242     }
243 
getSync4()244     public String getSync4() {
245         return getValues().getAsString(RawContacts.SYNC4);
246     }
247 
isDeleted()248     public boolean isDeleted() {
249         return getValues().getAsBoolean(RawContacts.DELETED);
250     }
251 
getContactId()252     public long getContactId() {
253         return getValues().getAsLong(Contacts.Entity.CONTACT_ID);
254     }
255 
isStarred()256     public boolean isStarred() {
257         return getValues().getAsBoolean(Contacts.STARRED);
258     }
259 
getAccountType(Context context)260     public AccountType getAccountType(Context context) {
261         return getAccountTypeManager(context).getAccountType(getAccountTypeString(), getDataSet());
262     }
263 
264     /**
265      * Sets the account name, account type, and data set strings.
266      * Valid combinations for account-name, account-type, data-set
267      * 1) null, null, null (local account)
268      * 2) non-null, non-null, null (valid account without data-set)
269      * 3) non-null, non-null, non-null (valid account with data-set)
270      */
setAccount(String accountName, String accountType, String dataSet)271     private void setAccount(String accountName, String accountType, String dataSet) {
272         final ContentValues values = getValues();
273         if (accountName == null) {
274             if (accountType == null && dataSet == null) {
275                 // This is a local account
276                 values.putNull(RawContacts.ACCOUNT_NAME);
277                 values.putNull(RawContacts.ACCOUNT_TYPE);
278                 values.putNull(RawContacts.DATA_SET);
279                 return;
280             }
281         } else {
282             if (accountType != null) {
283                 // This is a valid account, either with or without a dataSet.
284                 values.put(RawContacts.ACCOUNT_NAME, accountName);
285                 values.put(RawContacts.ACCOUNT_TYPE, accountType);
286                 if (dataSet == null) {
287                     values.putNull(RawContacts.DATA_SET);
288                 } else {
289                     values.put(RawContacts.DATA_SET, dataSet);
290                 }
291                 return;
292             }
293         }
294         throw new IllegalArgumentException(
295                 "Not a valid combination of account name, type, and data set.");
296     }
297 
setAccount(AccountWithDataSet accountWithDataSet)298     public void setAccount(AccountWithDataSet accountWithDataSet) {
299         if (accountWithDataSet != null) {
300             setAccount(accountWithDataSet.name, accountWithDataSet.type,
301                     accountWithDataSet.dataSet);
302         } else {
303             setAccount(null, null, null);
304         }
305     }
306 
setAccountToLocal()307     public void setAccountToLocal() {
308         setAccount(null, null, null);
309     }
310 
311     /**
312      * Creates and inserts a DataItem object that wraps the content values, and returns it.
313      */
addDataItemValues(ContentValues values)314     public void addDataItemValues(ContentValues values) {
315         addNamedDataItemValues(Data.CONTENT_URI, values);
316     }
317 
addNamedDataItemValues(Uri uri, ContentValues values)318     public NamedDataItem addNamedDataItemValues(Uri uri, ContentValues values) {
319         final NamedDataItem namedItem = new NamedDataItem(uri, values);
320         mDataItems.add(namedItem);
321         return namedItem;
322     }
323 
getContentValues()324     public ArrayList<ContentValues> getContentValues() {
325         final ArrayList<ContentValues> list = Lists.newArrayListWithCapacity(mDataItems.size());
326         for (NamedDataItem dataItem : mDataItems) {
327             if (Data.CONTENT_URI.equals(dataItem.mUri)) {
328                 list.add(dataItem.mContentValues);
329             }
330         }
331         return list;
332     }
333 
getDataItems()334     public List<DataItem> getDataItems() {
335         final ArrayList<DataItem> list = Lists.newArrayListWithCapacity(mDataItems.size());
336         for (NamedDataItem dataItem : mDataItems) {
337             if (Data.CONTENT_URI.equals(dataItem.mUri)) {
338                 list.add(DataItem.createFrom(dataItem.mContentValues));
339             }
340         }
341         return list;
342     }
343 
toString()344     public String toString() {
345         final StringBuilder sb = new StringBuilder();
346         sb.append("RawContact: ").append(mValues);
347         for (RawContact.NamedDataItem namedDataItem : mDataItems) {
348             sb.append("\n  ").append(namedDataItem.mUri);
349             sb.append("\n  -> ").append(namedDataItem.mContentValues);
350         }
351         return sb.toString();
352     }
353 
354     @Override
hashCode()355     public int hashCode() {
356         return Objects.hashCode(mValues, mDataItems);
357     }
358 
359     @Override
equals(Object obj)360     public boolean equals(Object obj) {
361         if (obj == null) return false;
362         if (getClass() != obj.getClass()) return false;
363 
364         RawContact other = (RawContact) obj;
365         return Objects.equal(mValues, other.mValues) &&
366                 Objects.equal(mDataItems, other.mDataItems);
367     }
368 }
369