1 /* 2 * Copyright (C) 2017 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.dialer.simulator.impl; 18 19 import android.content.ContentProviderOperation; 20 import android.content.Context; 21 import android.content.OperationApplicationException; 22 import android.graphics.Bitmap; 23 import android.graphics.Canvas; 24 import android.graphics.Color; 25 import android.graphics.Paint; 26 import android.os.RemoteException; 27 import android.provider.ContactsContract; 28 import android.provider.ContactsContract.CommonDataKinds.Phone; 29 import android.support.annotation.NonNull; 30 import android.support.annotation.Nullable; 31 import android.support.annotation.WorkerThread; 32 import android.text.TextUtils; 33 import com.android.dialer.common.Assert; 34 import com.google.auto.value.AutoValue; 35 import java.io.ByteArrayOutputStream; 36 import java.util.ArrayList; 37 import java.util.List; 38 39 /** Populates the device database with contacts. */ 40 public final class SimulatorContacts { 41 // Phone numbers from https://www.google.com/about/company/facts/locations/ 42 private static final Contact[] SIMPLE_CONTACTS = { 43 // US, contact with e164 number. 44 Contact.builder() 45 .setName("Michelangelo") 46 .addPhoneNumber(new PhoneNumber("+1-302-6365454", Phone.TYPE_MOBILE)) 47 .addEmail(new Email("m (at) example.com")) 48 .setIsStarred(true) 49 .setOrangePhoto() 50 .build(), 51 // US, contact with a non-e164 number. 52 Contact.builder() 53 .setName("Leonardo da Vinci") 54 .addPhoneNumber(new PhoneNumber("(425) 739-5600", Phone.TYPE_MOBILE)) 55 .addEmail(new Email("l (at) example.com")) 56 .setIsStarred(true) 57 .setBluePhoto() 58 .build(), 59 // UK, number where the (0) should be dropped. 60 Contact.builder() 61 .setName("Raphael") 62 .addPhoneNumber(new PhoneNumber("+44 (0) 20 7031 3000", Phone.TYPE_MOBILE)) 63 .addEmail(new Email("r (at) example.com")) 64 .setIsStarred(true) 65 .setRedPhoto() 66 .build(), 67 // US and Australia, contact with a long name and multiple phone numbers. 68 Contact.builder() 69 .setName("Donatello di Niccol di Betto Bardi") 70 .addPhoneNumber(new PhoneNumber("+1-650-2530000", Phone.TYPE_HOME)) 71 .addPhoneNumber(new PhoneNumber("+1 404-487-9000", Phone.TYPE_WORK)) 72 .addPhoneNumber(new PhoneNumber("+61 2 9374 4001", Phone.TYPE_FAX_HOME)) 73 .setIsStarred(true) 74 .setPurplePhoto() 75 .build(), 76 // US, phone number shared with another contact and 2nd phone number with wait and pause. 77 Contact.builder() 78 .setName("Splinter") 79 .addPhoneNumber(new PhoneNumber("+1-650-2530000", Phone.TYPE_HOME)) 80 .addPhoneNumber(new PhoneNumber("+1 303-245-0086;123,456", Phone.TYPE_WORK)) 81 .build(), 82 // France, number with Japanese name. 83 Contact.builder() 84 .setName("") 85 .addPhoneNumber(new PhoneNumber("+33 (0)1 42 68 53 00", Phone.TYPE_MOBILE)) 86 .build(), 87 // Israel, RTL name and non-e164 number. 88 Contact.builder() 89 .setName(" ") 90 .addPhoneNumber(new PhoneNumber("+33 (0)1 42 68 53 00", Phone.TYPE_MOBILE)) 91 .build(), 92 // UAE, RTL name. 93 Contact.builder() 94 .setName(" ") 95 .addPhoneNumber(new PhoneNumber("+971 4 4509500", Phone.TYPE_MOBILE)) 96 .build(), 97 // Brazil, contact with no name. 98 Contact.builder() 99 .addPhoneNumber(new PhoneNumber("+55-31-2128-6800", Phone.TYPE_MOBILE)) 100 .build(), 101 // Short number, contact with no name. 102 Contact.builder().addPhoneNumber(new PhoneNumber("611", Phone.TYPE_MOBILE)).build(), 103 // US, number with an anonymous prefix. 104 Contact.builder() 105 .setName("Anonymous") 106 .addPhoneNumber(new PhoneNumber("*86 512-343-5283", Phone.TYPE_MOBILE)) 107 .build(), 108 // None, contact with no phone number. 109 Contact.builder() 110 .setName("No Phone Number") 111 .addEmail(new Email("no (at) example.com")) 112 .setIsStarred(true) 113 .build(), 114 }; 115 116 @WorkerThread 117 public static void populateContacts(@NonNull Context context) { 118 Assert.isWorkerThread(); 119 ArrayList<ContentProviderOperation> operations = new ArrayList<>(); 120 for (Contact contact : SIMPLE_CONTACTS) { 121 addContact(contact, operations); 122 } 123 try { 124 context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations); 125 } catch (RemoteException | OperationApplicationException e) { 126 Assert.fail("error adding contacts: " + e); 127 } 128 } 129 130 private static void addContact(Contact contact, List<ContentProviderOperation> operations) { 131 int index = operations.size(); 132 133 operations.add( 134 ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) 135 .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, contact.getAccountType()) 136 .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, contact.getAccountName()) 137 .withValue(ContactsContract.RawContacts.STARRED, contact.getIsStarred() ? 1 : 0) 138 .withYieldAllowed(true) 139 .build()); 140 141 if (!TextUtils.isEmpty(contact.getName())) { 142 operations.add( 143 ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 144 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) 145 .withValue( 146 ContactsContract.Data.MIMETYPE, 147 ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) 148 .withValue( 149 ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, contact.getName()) 150 .build()); 151 } 152 153 if (contact.getPhotoStream() != null) { 154 operations.add( 155 ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 156 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) 157 .withValue( 158 ContactsContract.Data.MIMETYPE, 159 ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE) 160 .withValue( 161 ContactsContract.CommonDataKinds.Photo.PHOTO, 162 contact.getPhotoStream().toByteArray()) 163 .build()); 164 } 165 166 for (PhoneNumber phoneNumber : contact.getPhoneNumbers()) { 167 operations.add( 168 ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 169 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) 170 .withValue( 171 ContactsContract.Data.MIMETYPE, 172 ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) 173 .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber.value) 174 .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneNumber.type) 175 .withValue(ContactsContract.CommonDataKinds.Phone.LABEL, phoneNumber.label) 176 .build()); 177 } 178 179 for (Email email : contact.getEmails()) { 180 operations.add( 181 ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 182 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) 183 .withValue( 184 ContactsContract.Data.MIMETYPE, 185 ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) 186 .withValue(ContactsContract.CommonDataKinds.Email.DATA, email.value) 187 .withValue(ContactsContract.CommonDataKinds.Email.TYPE, email.type) 188 .withValue(ContactsContract.CommonDataKinds.Email.LABEL, email.label) 189 .build()); 190 } 191 } 192 193 @AutoValue 194 abstract static class Contact { 195 @NonNull 196 abstract String getAccountType(); 197 198 @NonNull 199 abstract String getAccountName(); 200 201 @Nullable 202 abstract String getName(); 203 204 abstract boolean getIsStarred(); 205 206 @Nullable 207 abstract ByteArrayOutputStream getPhotoStream(); 208 209 @NonNull 210 abstract List<PhoneNumber> getPhoneNumbers(); 211 212 @NonNull 213 abstract List<Email> getEmails(); 214 215 static Builder builder() { 216 return new AutoValue_SimulatorContacts_Contact.Builder() 217 .setAccountType("com.google") 218 .setAccountName("foo@example") 219 .setIsStarred(false) 220 .setPhoneNumbers(new ArrayList<>()) 221 .setEmails(new ArrayList<>()); 222 } 223 224 @AutoValue.Builder 225 abstract static class Builder { 226 @NonNull private final List<PhoneNumber> phoneNumbers = new ArrayList<>(); 227 @NonNull private final List<Email> emails = new ArrayList<>(); 228 229 abstract Builder setAccountType(@NonNull String accountType); 230 231 abstract Builder setAccountName(@NonNull String accountName); 232 233 abstract Builder setName(@NonNull String name); 234 235 abstract Builder setIsStarred(boolean isStarred); 236 237 abstract Builder setPhotoStream(ByteArrayOutputStream photoStream); 238 239 abstract Builder setPhoneNumbers(@NonNull List<PhoneNumber> phoneNumbers); 240 241 abstract Builder setEmails(@NonNull List<Email> emails); 242 243 abstract Contact build(); 244 245 Builder addPhoneNumber(PhoneNumber phoneNumber) { 246 phoneNumbers.add(phoneNumber); 247 return setPhoneNumbers(phoneNumbers); 248 } 249 250 Builder addEmail(Email email) { 251 emails.add(email); 252 return setEmails(emails); 253 } 254 255 Builder setRedPhoto() { 256 setPhotoStream(getPhotoStreamWithColor(Color.rgb(0xe3, 0x33, 0x1c))); 257 return this; 258 } 259 260 Builder setBluePhoto() { 261 setPhotoStream(getPhotoStreamWithColor(Color.rgb(0x00, 0xaa, 0xe6))); 262 return this; 263 } 264 265 Builder setOrangePhoto() { 266 setPhotoStream(getPhotoStreamWithColor(Color.rgb(0xea, 0x95, 0x00))); 267 return this; 268 } 269 270 Builder setPurplePhoto() { 271 setPhotoStream(getPhotoStreamWithColor(Color.rgb(0x99, 0x5a, 0xa0))); 272 return this; 273 } 274 275 /** Creates a contact photo with a green background and a circle of the given color. */ 276 private static ByteArrayOutputStream getPhotoStreamWithColor(int color) { 277 int width = 300; 278 int height = 300; 279 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 280 Canvas canvas = new Canvas(bitmap); 281 canvas.drawColor(Color.argb(0xff, 0x4c, 0x9c, 0x23)); 282 Paint paint = new Paint(); 283 paint.setColor(color); 284 paint.setStyle(Paint.Style.FILL); 285 canvas.drawCircle(width / 2, height / 2, width / 3, paint); 286 287 ByteArrayOutputStream photoStream = new ByteArrayOutputStream(); 288 bitmap.compress(Bitmap.CompressFormat.PNG, 75, photoStream); 289 return photoStream; 290 } 291 } 292 } 293 294 static class PhoneNumber { 295 public final String value; 296 public final int type; 297 public final String label; 298 299 PhoneNumber(String value, int type) { 300 this.value = value; 301 this.type = type; 302 label = "simulator phone number"; 303 } 304 } 305 306 static class Email { 307 public final String value; 308 public final int type; 309 public final String label; 310 311 Email(String simpleEmail) { 312 value = simpleEmail; 313 type = ContactsContract.CommonDataKinds.Email.TYPE_WORK; 314 label = "simulator email"; 315 } 316 } 317 318 private SimulatorContacts() {} 319 } 320