• 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[3], operations);
156     addContact(SIMPLE_CONTACTS[5], operations);
157     try {
158       context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
159     } catch (RemoteException | OperationApplicationException e) {
160       Assert.fail("error adding contacts: " + e);
161     }
162   }
163 
164   @WorkerThread
populateContacts(@onNull Context context)165   public static void populateContacts(@NonNull Context context) {
166     populateContacts(context, false);
167   }
168 
169   @WorkerThread
deleteAllContacts(@onNull Context context)170   public static void deleteAllContacts(@NonNull Context context) {
171     Assert.isWorkerThread();
172     try {
173       context
174           .getContentResolver()
175           .applyBatch(
176               ContactsContract.AUTHORITY,
177               new ArrayList<>(
178                   Arrays.asList(
179                       ContentProviderOperation.newDelete(RawContacts.CONTENT_URI).build())));
180     } catch (RemoteException | OperationApplicationException e) {
181       Assert.fail("failed to delete contacts: " + e);
182     }
183   }
184 
addContact(Contact contact, List<ContentProviderOperation> operations)185   private static void addContact(Contact contact, List<ContentProviderOperation> operations) {
186     int index = operations.size();
187 
188     operations.add(
189         ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
190             .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, contact.getAccountType())
191             .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, contact.getAccountName())
192             .withValue(ContactsContract.RawContacts.STARRED, contact.getIsStarred() ? 1 : 0)
193             .withValue(
194                 ContactsContract.RawContacts.PINNED,
195                 contact.getIsStarred() ? contact.getPinned() : 0)
196             .withYieldAllowed(true)
197             .build());
198 
199     if (!TextUtils.isEmpty(contact.getName())) {
200       operations.add(
201           ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
202               .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index)
203               .withValue(
204                   ContactsContract.Data.MIMETYPE,
205                   ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
206               .withValue(
207                   ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, contact.getName())
208               .build());
209     }
210 
211     if (contact.getPhotoStream() != null) {
212       operations.add(
213           ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
214               .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index)
215               .withValue(
216                   ContactsContract.Data.MIMETYPE,
217                   ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
218               .withValue(
219                   ContactsContract.CommonDataKinds.Photo.PHOTO,
220                   contact.getPhotoStream().toByteArray())
221               .build());
222     }
223 
224     for (PhoneNumber phoneNumber : contact.getPhoneNumbers()) {
225       operations.add(
226           ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
227               .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index)
228               .withValue(
229                   ContactsContract.Data.MIMETYPE,
230                   ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
231               .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber.value)
232               .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneNumber.type)
233               .withValue(ContactsContract.CommonDataKinds.Phone.LABEL, phoneNumber.label)
234               .build());
235     }
236 
237     for (Email email : contact.getEmails()) {
238       operations.add(
239           ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
240               .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index)
241               .withValue(
242                   ContactsContract.Data.MIMETYPE,
243                   ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
244               .withValue(ContactsContract.CommonDataKinds.Email.DATA, email.value)
245               .withValue(ContactsContract.CommonDataKinds.Email.TYPE, email.type)
246               .withValue(ContactsContract.CommonDataKinds.Email.LABEL, email.label)
247               .build());
248     }
249   }
250 
251   @AutoValue
252   abstract static class Contact {
253     @NonNull
getAccountType()254     abstract String getAccountType();
255 
256     @NonNull
getAccountName()257     abstract String getAccountName();
258 
259     @Nullable
getName()260     abstract String getName();
261 
getIsStarred()262     abstract boolean getIsStarred();
263 
getPinned()264     abstract int getPinned();
265 
266     @Nullable
getPhotoStream()267     abstract ByteArrayOutputStream getPhotoStream();
268 
269     @NonNull
getPhoneNumbers()270     abstract List<PhoneNumber> getPhoneNumbers();
271 
272     @NonNull
getEmails()273     abstract List<Email> getEmails();
274 
builder()275     static Builder builder() {
276       return new AutoValue_ContactsPopulator_Contact.Builder()
277           .setAccountType("com.google")
278           .setAccountName("foo@example")
279           .setPinned(0)
280           .setIsStarred(false)
281           .setPhoneNumbers(new ArrayList<>())
282           .setEmails(new ArrayList<>());
283     }
284 
285     @AutoValue.Builder
286     abstract static class Builder {
287       @NonNull private final List<PhoneNumber> phoneNumbers = new ArrayList<>();
288       @NonNull private final List<Email> emails = new ArrayList<>();
289 
setAccountType(@onNull String accountType)290       abstract Builder setAccountType(@NonNull String accountType);
291 
setAccountName(@onNull String accountName)292       abstract Builder setAccountName(@NonNull String accountName);
293 
setName(@onNull String name)294       abstract Builder setName(@NonNull String name);
295 
setIsStarred(boolean isStarred)296       abstract Builder setIsStarred(boolean isStarred);
297 
setPinned(int position)298       abstract Builder setPinned(int position);
299 
setPhotoStream(ByteArrayOutputStream photoStream)300       abstract Builder setPhotoStream(ByteArrayOutputStream photoStream);
301 
setPhoneNumbers(@onNull List<PhoneNumber> phoneNumbers)302       abstract Builder setPhoneNumbers(@NonNull List<PhoneNumber> phoneNumbers);
303 
setEmails(@onNull List<Email> emails)304       abstract Builder setEmails(@NonNull List<Email> emails);
305 
build()306       abstract Contact build();
307 
addPhoneNumber(PhoneNumber phoneNumber)308       Builder addPhoneNumber(PhoneNumber phoneNumber) {
309         phoneNumbers.add(phoneNumber);
310         return setPhoneNumbers(phoneNumbers);
311       }
312 
addEmail(Email email)313       Builder addEmail(Email email) {
314         emails.add(email);
315         return setEmails(emails);
316       }
317 
setRedPhoto()318       Builder setRedPhoto() {
319         setPhotoStream(getPhotoStreamWithColor(Color.rgb(0xe3, 0x33, 0x1c)));
320         return this;
321       }
322 
setBluePhoto()323       Builder setBluePhoto() {
324         setPhotoStream(getPhotoStreamWithColor(Color.rgb(0x00, 0xaa, 0xe6)));
325         return this;
326       }
327 
setOrangePhoto()328       Builder setOrangePhoto() {
329         setPhotoStream(getPhotoStreamWithColor(Color.rgb(0xea, 0x95, 0x00)));
330         return this;
331       }
332 
setPurplePhoto()333       Builder setPurplePhoto() {
334         setPhotoStream(getPhotoStreamWithColor(Color.rgb(0x99, 0x5a, 0xa0)));
335         return this;
336       }
337 
338       /** Creates a contact photo with a green background and a circle of the given color. */
getPhotoStreamWithColor(int color)339       private static ByteArrayOutputStream getPhotoStreamWithColor(int color) {
340         int width = 300;
341         int height = 300;
342         Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
343         Canvas canvas = new Canvas(bitmap);
344         canvas.drawColor(Color.argb(0xff, 0x4c, 0x9c, 0x23));
345         Paint paint = new Paint();
346         paint.setColor(color);
347         paint.setStyle(Paint.Style.FILL);
348         canvas.drawCircle(width / 2, height / 2, width / 3, paint);
349 
350         ByteArrayOutputStream photoStream = new ByteArrayOutputStream();
351         bitmap.compress(Bitmap.CompressFormat.PNG, 75, photoStream);
352         return photoStream;
353       }
354     }
355   }
356 
357   static class PhoneNumber {
358     public final String value;
359     public final int type;
360     public final String label;
361 
PhoneNumber(String value, int type)362     PhoneNumber(String value, int type) {
363       this.value = value;
364       this.type = type;
365       label = "simulator phone number";
366     }
367   }
368 
369   static class Email {
370     public final String value;
371     public final int type;
372     public final String label;
373 
Email(String simpleEmail)374     Email(String simpleEmail) {
375       value = simpleEmail;
376       type = ContactsContract.CommonDataKinds.Email.TYPE_WORK;
377       label = "simulator email";
378     }
379   }
380 
ContactsPopulator()381   private ContactsPopulator() {}
382 }
383