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