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