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