1 /* 2 * Copyright (C) 2019 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.car.telephony.common; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.database.Cursor; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import android.provider.ContactsContract; 25 import android.provider.ContactsContract.CommonDataKinds.Phone; 26 27 import androidx.annotation.NonNull; 28 import androidx.annotation.Nullable; 29 30 import java.util.Objects; 31 32 /** 33 * Contact phone number and its meta data. 34 */ 35 public class PhoneNumber implements Parcelable { 36 37 private final I18nPhoneNumberWrapper mI18nPhoneNumber; 38 @NonNull 39 private final String mAccountName; 40 @NonNull 41 private final String mAccountType; 42 43 private int mType; 44 @Nullable 45 private String mLabel; 46 private boolean mIsPrimary; 47 private long mId; 48 private int mDataVersion; 49 50 /** The favorite bit is from local database, presenting a 51 * {@link com.android.car.dialer.storage.FavoriteNumberEntity}. */ 52 private boolean mIsFavorite; 53 fromCursor(Context context, Cursor cursor)54 static PhoneNumber fromCursor(Context context, Cursor cursor) { 55 int typeColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE); 56 int labelColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.LABEL); 57 int numberColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER); 58 int rawDataIdColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone._ID); 59 int dataVersionColumn = cursor.getColumnIndex( 60 ContactsContract.CommonDataKinds.Phone.DATA_VERSION); 61 // IS_PRIMARY means primary entry of the raw contact and IS_SUPER_PRIMARY means primary 62 // entry of the aggregated contact. It is guaranteed that only one data entry is super 63 // primary. 64 int isPrimaryColumn = cursor.getColumnIndex( 65 ContactsContract.CommonDataKinds.Phone.IS_SUPER_PRIMARY); 66 int accountNameColumn = cursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_NAME); 67 int accountTypeColumn = cursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_TYPE); 68 return PhoneNumber.newInstance( 69 context, 70 cursor.getString(numberColumn), 71 cursor.getInt(typeColumn), 72 cursor.getString(labelColumn), 73 cursor.getInt(isPrimaryColumn) > 0, 74 cursor.getLong(rawDataIdColumn), 75 cursor.getString(accountNameColumn), 76 cursor.getString(accountTypeColumn), 77 cursor.getInt(dataVersionColumn)); 78 } 79 80 /** 81 * Creates a new {@link PhoneNumber}. 82 * 83 * @param rawNumber A potential phone number. 84 * @param type The phone number type. See more at {@link Phone#TYPE} 85 * @param label The user defined label. See more at {@link Phone#LABEL} 86 * @param isPrimary Whether this is the primary entry of the aggregated contact it belongs 87 * to. See more at {@link Phone#IS_SUPER_PRIMARY}. 88 * @param id The unique key for raw contact entry containing the phone number entity. 89 * See more at {@link Phone#_ID} 90 * @param dataVersion The dataVersion of the raw contact entry record. See more at {@link 91 * Phone#DATA_VERSION} 92 */ newInstance(Context context, String rawNumber, int type, @Nullable String label, boolean isPrimary, long id, String accountName, String accountType, int dataVersion)93 public static PhoneNumber newInstance(Context context, String rawNumber, int type, 94 @Nullable String label, boolean isPrimary, long id, String accountName, 95 String accountType, int dataVersion) { 96 I18nPhoneNumberWrapper i18nPhoneNumber = I18nPhoneNumberWrapper.Factory.INSTANCE.get( 97 context, rawNumber); 98 return new PhoneNumber(i18nPhoneNumber, type, label, isPrimary, id, accountName, 99 accountType, dataVersion); 100 } 101 PhoneNumber(I18nPhoneNumberWrapper i18nNumber, int type, @Nullable String label, boolean isPrimary, long id, String accountName, String accountType, int dataVersion)102 private PhoneNumber(I18nPhoneNumberWrapper i18nNumber, int type, @Nullable String label, 103 boolean isPrimary, long id, String accountName, String accountType, int dataVersion) { 104 mI18nPhoneNumber = i18nNumber; 105 mType = type; 106 mLabel = label; 107 mIsPrimary = isPrimary; 108 mId = id; 109 mAccountName = accountName == null ? "" : accountName; 110 mAccountType = accountType == null ? "" : accountType; 111 mDataVersion = dataVersion; 112 } 113 114 @Override equals(Object obj)115 public boolean equals(Object obj) { 116 return obj instanceof PhoneNumber 117 && mI18nPhoneNumber.equals(((PhoneNumber) obj).mI18nPhoneNumber) 118 && mAccountName.equals(((PhoneNumber) obj).mAccountName) 119 && mAccountType.equals(((PhoneNumber) obj).mAccountType); 120 } 121 122 @Override hashCode()123 public int hashCode() { 124 return Objects.hash(mI18nPhoneNumber, mAccountName, mAccountType); 125 } 126 127 /** 128 * Returns if the phone number is the primary entry for the aggregated contact it belongs to. 129 * See more at {@link Phone#IS_SUPER_PRIMARY}. 130 */ isPrimary()131 public boolean isPrimary() { 132 return mIsPrimary; 133 } 134 135 /** 136 * Returns a human readable string label. For example, Home, Work, etc. 137 */ getReadableLabel(Resources res)138 public CharSequence getReadableLabel(Resources res) { 139 return Phone.getTypeLabel(res, mType, mLabel); 140 } 141 142 /** 143 * Gets a phone number in the international format if valid. Otherwise, returns the raw number. 144 */ getNumber()145 public String getNumber() { 146 return mI18nPhoneNumber.getNumber(); 147 } 148 149 /** 150 * Returns the raw number, the number that is input by the user 151 */ getRawNumber()152 public String getRawNumber() { 153 return mI18nPhoneNumber.getRawNumber(); 154 } 155 156 /** 157 * Returns the format independent i18n {@link I18nPhoneNumberWrapper wrapper} class. 158 */ getI18nPhoneNumberWrapper()159 public I18nPhoneNumberWrapper getI18nPhoneNumberWrapper() { 160 return mI18nPhoneNumber; 161 } 162 163 /** 164 * Gets the type of phone number, for example Home or Work. Possible values are defined in 165 * {@link Phone}. 166 */ getType()167 public int getType() { 168 return mType; 169 } 170 getId()171 public long getId() { 172 return mId; 173 } 174 175 @Nullable getAccountName()176 public String getAccountName() { 177 return mAccountName; 178 } 179 180 @Nullable getAccountType()181 public String getAccountType() { 182 return mAccountType; 183 } 184 185 /** 186 * Updates the favorite bit, which is local database. See 187 * {@link com.android.car.dialer.storage.FavoriteNumberDatabase}. 188 */ setIsFavorite(boolean isFavorite)189 public void setIsFavorite(boolean isFavorite) { 190 mIsFavorite = isFavorite; 191 } 192 193 /** Returns if the phone number is favorite entry. */ isFavorite()194 public boolean isFavorite() { 195 return mIsFavorite; 196 } 197 198 /** 199 * Each contact may have a few sources with the same phone number. Merge same phone numbers as 200 * one. 201 * 202 * <p>As long as one of those phone numbers is primary entry of the aggregated contact, mark 203 * the merged phone number as primary. 204 */ merge(PhoneNumber phoneNumber)205 public PhoneNumber merge(PhoneNumber phoneNumber) { 206 if (equals(phoneNumber)) { 207 if (mDataVersion < phoneNumber.mDataVersion) { 208 mDataVersion = phoneNumber.mDataVersion; 209 mId = phoneNumber.mId; 210 mIsPrimary |= phoneNumber.mIsPrimary; 211 mType = phoneNumber.mType; 212 mLabel = phoneNumber.mLabel; 213 } 214 } 215 return this; 216 } 217 218 /** 219 * Gets the user defined label for the the contact method. 220 */ 221 @Nullable getLabel()222 public String getLabel() { 223 return mLabel; 224 } 225 226 @Override toString()227 public String toString() { 228 return getNumber() + " " + mAccountName + " " + mAccountType; 229 } 230 231 @Override describeContents()232 public int describeContents() { 233 return 0; 234 } 235 236 @Override writeToParcel(Parcel dest, int flags)237 public void writeToParcel(Parcel dest, int flags) { 238 dest.writeInt(mType); 239 dest.writeString(mLabel); 240 dest.writeParcelable(mI18nPhoneNumber, flags); 241 dest.writeBoolean(mIsPrimary); 242 dest.writeLong(mId); 243 dest.writeString(mAccountName); 244 dest.writeString(mAccountType); 245 dest.writeInt(mDataVersion); 246 dest.writeBoolean(mIsFavorite); 247 } 248 249 public static Creator<PhoneNumber> CREATOR = new Creator<PhoneNumber>() { 250 @Override 251 public PhoneNumber createFromParcel(Parcel source) { 252 int type = source.readInt(); 253 String label = source.readString(); 254 I18nPhoneNumberWrapper i18nPhoneNumberWrapper = source.readParcelable( 255 I18nPhoneNumberWrapper.class.getClassLoader()); 256 boolean isPrimary = source.readBoolean(); 257 long id = source.readLong(); 258 String accountName = source.readString(); 259 String accountType = source.readString(); 260 int dataVersion = source.readInt(); 261 PhoneNumber phoneNumber = new PhoneNumber(i18nPhoneNumberWrapper, type, label, 262 isPrimary, id, accountName, accountType, dataVersion); 263 phoneNumber.setIsFavorite(source.readBoolean()); 264 return phoneNumber; 265 } 266 267 @Override 268 public PhoneNumber[] newArray(int size) { 269 return new PhoneNumber[size]; 270 } 271 }; 272 } 273