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.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.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 224 /** 225 * Returns the account type and data set of the raw contact. 226 */ getAccountTypeAndDataSetString()227 public String getAccountTypeAndDataSetString() { 228 return getValues().getAsString(RawContacts.ACCOUNT_TYPE_AND_DATA_SET); 229 } 230 isDirty()231 public boolean isDirty() { 232 return getValues().getAsBoolean(RawContacts.DIRTY); 233 } 234 getVersion()235 public long getVersion() { 236 return getValues().getAsLong(RawContacts.DIRTY); 237 } 238 getSourceId()239 public String getSourceId() { 240 return getValues().getAsString(RawContacts.SOURCE_ID); 241 } 242 getSync1()243 public String getSync1() { 244 return getValues().getAsString(RawContacts.SYNC1); 245 } 246 getSync2()247 public String getSync2() { 248 return getValues().getAsString(RawContacts.SYNC2); 249 } 250 getSync3()251 public String getSync3() { 252 return getValues().getAsString(RawContacts.SYNC3); 253 } 254 getSync4()255 public String getSync4() { 256 return getValues().getAsString(RawContacts.SYNC4); 257 } 258 isDeleted()259 public boolean isDeleted() { 260 return getValues().getAsBoolean(RawContacts.DELETED); 261 } 262 isNameVerified()263 public boolean isNameVerified() { 264 return getValues().getAsBoolean(RawContacts.NAME_VERIFIED); 265 } 266 getContactId()267 public long getContactId() { 268 return getValues().getAsLong(Contacts.Entity.CONTACT_ID); 269 } 270 isStarred()271 public boolean isStarred() { 272 return getValues().getAsBoolean(Contacts.STARRED); 273 } 274 getAccountType(Context context)275 public AccountType getAccountType(Context context) { 276 return getAccountTypeManager(context).getAccountType(getAccountTypeString(), getDataSet()); 277 } 278 279 /** 280 * Sets the account name, account type, and data set strings. 281 * Valid combinations for account-name, account-type, data-set 282 * 1) null, null, null (local account) 283 * 2) non-null, non-null, null (valid account without data-set) 284 * 3) non-null, non-null, non-null (valid account with data-set) 285 */ setAccount(String accountName, String accountType, String dataSet)286 private void setAccount(String accountName, String accountType, String dataSet) { 287 final ContentValues values = getValues(); 288 if (accountName == null) { 289 if (accountType == null && dataSet == null) { 290 // This is a local account 291 values.putNull(RawContacts.ACCOUNT_NAME); 292 values.putNull(RawContacts.ACCOUNT_TYPE); 293 values.putNull(RawContacts.DATA_SET); 294 return; 295 } 296 } else { 297 if (accountType != null) { 298 // This is a valid account, either with or without a dataSet. 299 values.put(RawContacts.ACCOUNT_NAME, accountName); 300 values.put(RawContacts.ACCOUNT_TYPE, accountType); 301 if (dataSet == null) { 302 values.putNull(RawContacts.DATA_SET); 303 } else { 304 values.put(RawContacts.DATA_SET, dataSet); 305 } 306 return; 307 } 308 } 309 throw new IllegalArgumentException( 310 "Not a valid combination of account name, type, and data set."); 311 } 312 setAccount(AccountWithDataSet accountWithDataSet)313 public void setAccount(AccountWithDataSet accountWithDataSet) { 314 setAccount(accountWithDataSet.name, accountWithDataSet.type, accountWithDataSet.dataSet); 315 } 316 setAccountToLocal()317 public void setAccountToLocal() { 318 setAccount(null, null, null); 319 } 320 321 /** 322 * Creates and inserts a DataItem object that wraps the content values, and returns it. 323 */ addDataItemValues(ContentValues values)324 public void addDataItemValues(ContentValues values) { 325 addNamedDataItemValues(Data.CONTENT_URI, values); 326 } 327 addNamedDataItemValues(Uri uri, ContentValues values)328 public NamedDataItem addNamedDataItemValues(Uri uri, ContentValues values) { 329 final NamedDataItem namedItem = new NamedDataItem(uri, values); 330 mDataItems.add(namedItem); 331 return namedItem; 332 } 333 getContentValues()334 public ArrayList<ContentValues> getContentValues() { 335 final ArrayList<ContentValues> list = Lists.newArrayListWithCapacity(mDataItems.size()); 336 for (NamedDataItem dataItem : mDataItems) { 337 if (Data.CONTENT_URI.equals(dataItem.mUri)) { 338 list.add(dataItem.mContentValues); 339 } 340 } 341 return list; 342 } 343 getDataItems()344 public List<DataItem> getDataItems() { 345 final ArrayList<DataItem> list = Lists.newArrayListWithCapacity(mDataItems.size()); 346 for (NamedDataItem dataItem : mDataItems) { 347 if (Data.CONTENT_URI.equals(dataItem.mUri)) { 348 list.add(DataItem.createFrom(dataItem.mContentValues)); 349 } 350 } 351 return list; 352 } 353 toString()354 public String toString() { 355 final StringBuilder sb = new StringBuilder(); 356 sb.append("RawContact: ").append(mValues); 357 for (RawContact.NamedDataItem namedDataItem : mDataItems) { 358 sb.append("\n ").append(namedDataItem.mUri); 359 sb.append("\n -> ").append(namedDataItem.mContentValues); 360 } 361 return sb.toString(); 362 } 363 364 @Override hashCode()365 public int hashCode() { 366 return Objects.hashCode(mValues, mDataItems); 367 } 368 369 @Override equals(Object obj)370 public boolean equals(Object obj) { 371 if (obj == null) return false; 372 if (getClass() != obj.getClass()) return false; 373 374 RawContact other = (RawContact) obj; 375 return Objects.equal(mValues, other.mValues) && 376 Objects.equal(mDataItems, other.mDataItems); 377 } 378 } 379