blob: b5b662687b50a8f4076c1c20cf74ce1e7395b887 [file] [log] [blame]
Marcus Hagerott819214d2016-09-29 14:58:27 -07001/*
2 * Copyright (C) 2016 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 */
16package com.android.contacts.common.database;
17
Marcus Hagerott66e8b222016-10-23 15:41:55 -070018import android.annotation.TargetApi;
Marcus Hagerott819214d2016-09-29 14:58:27 -070019import android.content.ContentProviderOperation;
20import android.content.ContentProviderResult;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.OperationApplicationException;
Marcus Hagerott2ea50f12016-10-21 09:30:24 -070024import android.content.pm.PackageManager;
Marcus Hagerott819214d2016-09-29 14:58:27 -070025import android.database.Cursor;
26import android.net.Uri;
John Shao3ed3af22016-10-18 18:31:29 -070027import android.os.Build;
Marcus Hagerott819214d2016-09-29 14:58:27 -070028import android.os.RemoteException;
29import android.provider.BaseColumns;
30import android.provider.ContactsContract;
Marcus Hagerott2aa31982016-10-25 14:36:25 -070031import android.provider.ContactsContract.CommonDataKinds.Phone;
Marcus Hagerott19d7eca2016-11-21 16:05:31 -080032import android.provider.ContactsContract.CommonDataKinds.StructuredName;
33import android.provider.ContactsContract.Data;
Marcus Hagerott6c42b4c2016-10-31 14:59:53 -070034import android.provider.ContactsContract.RawContacts;
Marcus Hagerott819214d2016-09-29 14:58:27 -070035import android.support.annotation.VisibleForTesting;
Marcus Hagerott6c42b4c2016-10-31 14:59:53 -070036import android.support.v4.util.ArrayMap;
37import android.support.v4.util.ArraySet;
John Shao3ed3af22016-10-18 18:31:29 -070038import android.telephony.SubscriptionInfo;
39import android.telephony.SubscriptionManager;
40import android.telephony.TelephonyManager;
Marcus Hagerott19d7eca2016-11-21 16:05:31 -080041import android.text.TextUtils;
Marcus Hagerott66e8b222016-10-23 15:41:55 -070042import android.util.SparseArray;
Marcus Hagerott819214d2016-09-29 14:58:27 -070043
Marcus Hagerott2aa31982016-10-25 14:36:25 -070044import com.android.contacts.R;
Marcus Hagerott66e8b222016-10-23 15:41:55 -070045import com.android.contacts.common.compat.CompatUtils;
46import com.android.contacts.common.model.SimCard;
Marcus Hagerott819214d2016-09-29 14:58:27 -070047import com.android.contacts.common.model.SimContact;
48import com.android.contacts.common.model.account.AccountWithDataSet;
Marcus Hagerott2ea50f12016-10-21 09:30:24 -070049import com.android.contacts.common.util.PermissionsUtil;
John Shao3ed3af22016-10-18 18:31:29 -070050import com.android.contacts.util.SharedPreferenceUtil;
Marcus Hagerott2aa31982016-10-25 14:36:25 -070051import com.google.common.base.Joiner;
Marcus Hagerott819214d2016-09-29 14:58:27 -070052
53import java.util.ArrayList;
Marcus Hagerott2aa31982016-10-25 14:36:25 -070054import java.util.Arrays;
John Shao3ed3af22016-10-18 18:31:29 -070055import java.util.Collections;
Marcus Hagerott6c42b4c2016-10-31 14:59:53 -070056import java.util.HashMap;
Marcus Hagerott19d7eca2016-11-21 16:05:31 -080057import java.util.Iterator;
Marcus Hagerott819214d2016-09-29 14:58:27 -070058import java.util.List;
Marcus Hagerott6c42b4c2016-10-31 14:59:53 -070059import java.util.Map;
60import java.util.Set;
Marcus Hagerott819214d2016-09-29 14:58:27 -070061
62/**
63 * Provides data access methods for loading contacts from a SIM card and and migrating these
64 * SIM contacts to a CP2 account.
65 */
66public class SimContactDao {
Marcus Hagerott2ea50f12016-10-21 09:30:24 -070067 private static final String TAG = "SimContactDao";
68
Marcus Hagerott2aa31982016-10-25 14:36:25 -070069 // Maximum number of SIM contacts to import in a single ContentResolver.applyBatch call.
70 // This is necessary to avoid TransactionTooLargeException when there are a large number of
71 // contacts. This has been tested on Nexus 6 NME70B and is probably be conservative enough
72 // to work on any phone.
73 private static final int IMPORT_MAX_BATCH_SIZE = 300;
74
Marcus Hagerott7333c372016-11-07 09:40:20 -080075 // How many SIM contacts to consider in a single query. This prevents hitting the SQLite
76 // query parameter limit.
77 static final int QUERY_MAX_BATCH_SIZE = 100;
78
Marcus Hagerott66e8b222016-10-23 15:41:55 -070079 // Set to true for manual testing on an emulator or phone without a SIM card
80 // DO NOT SUBMIT if set to true
81 private static final boolean USE_FAKE_INSTANCE = false;
82
Marcus Hagerott819214d2016-09-29 14:58:27 -070083 @VisibleForTesting
84 public static final Uri ICC_CONTENT_URI = Uri.parse("content://icc/adn");
85
86 public static String _ID = BaseColumns._ID;
87 public static String NAME = "name";
88 public static String NUMBER = "number";
89 public static String EMAILS = "emails";
90
John Shao3ed3af22016-10-18 18:31:29 -070091 private final Context mContext;
92 private final ContentResolver mResolver;
93 private final TelephonyManager mTelephonyManager;
Marcus Hagerott819214d2016-09-29 14:58:27 -070094
Marcus Hagerott2aa31982016-10-25 14:36:25 -070095 private SimContactDao(Context context) {
John Shao3ed3af22016-10-18 18:31:29 -070096 mContext = context;
97 mResolver = context.getContentResolver();
98 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
Marcus Hagerott819214d2016-09-29 14:58:27 -070099 }
100
John Shao3ed3af22016-10-18 18:31:29 -0700101
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700102 public Context getContext() {
103 return mContext;
104 }
105
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700106 public boolean canReadSimContacts() {
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700107 // Require SIM_STATE_READY because the TelephonyManager methods related to SIM require
108 // this state
Marcus Hagerott2ea50f12016-10-21 09:30:24 -0700109 return hasTelephony() && hasPermissions() &&
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700110 mTelephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY;
John Shao3ed3af22016-10-18 18:31:29 -0700111 }
112
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700113 public List<SimCard> getSimCards() {
114 if (!canReadSimContacts()) {
115 return Collections.emptyList();
John Shao3ed3af22016-10-18 18:31:29 -0700116 }
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700117 final List<SimCard> sims = CompatUtils.isMSIMCompatible() ?
118 getSimCardsFromSubscriptions() :
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700119 Collections.singletonList(SimCard.create(mTelephonyManager,
120 mContext.getString(R.string.single_sim_display_label)));
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700121 return SharedPreferenceUtil.restoreSimStates(mContext, sims);
John Shao3ed3af22016-10-18 18:31:29 -0700122 }
123
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700124 public List<SimCard> getSimCardsWithContacts() {
125 final List<SimCard> result = new ArrayList<>();
126 for (SimCard sim : getSimCards()) {
127 result.add(sim.withContacts(loadContactsForSim(sim)));
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700128 }
129 return result;
Marcus Hagerott819214d2016-09-29 14:58:27 -0700130 }
131
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700132 public ArrayList<SimContact> loadContactsForSim(SimCard sim) {
133 if (sim.hasValidSubscriptionId()) {
134 return loadSimContacts(sim.getSubscriptionId());
135 }
136 return loadSimContacts();
137 }
138
Marcus Hagerott819214d2016-09-29 14:58:27 -0700139 public ArrayList<SimContact> loadSimContacts(int subscriptionId) {
140 return loadFrom(ICC_CONTENT_URI.buildUpon()
141 .appendPath("subId")
142 .appendPath(String.valueOf(subscriptionId))
143 .build());
144 }
145
146 public ArrayList<SimContact> loadSimContacts() {
147 return loadFrom(ICC_CONTENT_URI);
148 }
149
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700150 public ContentProviderResult[] importContacts(List<SimContact> contacts,
151 AccountWithDataSet targetAccount)
152 throws RemoteException, OperationApplicationException {
153 if (contacts.size() < IMPORT_MAX_BATCH_SIZE) {
154 return importBatch(contacts, targetAccount);
155 }
156 final List<ContentProviderResult> results = new ArrayList<>();
157 for (int i = 0; i < contacts.size(); i += IMPORT_MAX_BATCH_SIZE) {
158 results.addAll(Arrays.asList(importBatch(
159 contacts.subList(i, Math.min(contacts.size(), i + IMPORT_MAX_BATCH_SIZE)),
160 targetAccount)));
161 }
162 return results.toArray(new ContentProviderResult[results.size()]);
163 }
164
165 public void persistSimState(SimCard sim) {
166 SharedPreferenceUtil.persistSimStates(mContext, Collections.singletonList(sim));
167 }
168
169 public void persistSimStates(List<SimCard> simCards) {
170 SharedPreferenceUtil.persistSimStates(mContext, simCards);
171 }
172
173 public SimCard getFirstSimCard() {
174 return getSimBySubscriptionId(SimCard.NO_SUBSCRIPTION_ID);
175 }
176
177 public SimCard getSimBySubscriptionId(int subscriptionId) {
Marcus Hagerott76c06332016-11-08 13:33:50 -0800178 final List<SimCard> sims = SharedPreferenceUtil.restoreSimStates(mContext, getSimCards());
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700179 if (subscriptionId == SimCard.NO_SUBSCRIPTION_ID && !sims.isEmpty()) {
180 return sims.get(0);
181 }
182 for (SimCard sim : getSimCards()) {
183 if (sim.getSubscriptionId() == subscriptionId) {
184 return sim;
185 }
186 }
187 return null;
188 }
189
Marcus Hagerott19d7eca2016-11-21 16:05:31 -0800190 /**
191 * Finds SIM contacts that exist in CP2 and associates the account of the CP2 contact with
192 * the SIM contact
193 */
Marcus Hagerott6c42b4c2016-10-31 14:59:53 -0700194 public Map<AccountWithDataSet, Set<SimContact>> findAccountsOfExistingSimContacts(
195 List<SimContact> contacts) {
196 final Map<AccountWithDataSet, Set<SimContact>> result = new ArrayMap<>();
Marcus Hagerott7333c372016-11-07 09:40:20 -0800197 for (int i = 0; i < contacts.size(); i += QUERY_MAX_BATCH_SIZE) {
Marcus Hagerott6c42b4c2016-10-31 14:59:53 -0700198 findAccountsOfExistingSimContacts(
Marcus Hagerott7333c372016-11-07 09:40:20 -0800199 contacts.subList(i, Math.min(contacts.size(), i + QUERY_MAX_BATCH_SIZE)),
Marcus Hagerott6c42b4c2016-10-31 14:59:53 -0700200 result);
201 }
202 return result;
203 }
204
205 private void findAccountsOfExistingSimContacts(List<SimContact> contacts,
206 Map<AccountWithDataSet, Set<SimContact>> result) {
207 final Map<Long, List<SimContact>> rawContactToSimContact = new HashMap<>();
208 Collections.sort(contacts, SimContact.compareByPhoneThenName());
209
210 final Cursor dataCursor = queryRawContactsForSimContacts(contacts);
211
212 try {
213 while (dataCursor.moveToNext()) {
214 final String number = DataQuery.getPhoneNumber(dataCursor);
215 final String name = DataQuery.getDisplayName(dataCursor);
216
217 final int index = SimContact.findByPhoneAndName(contacts, number, name);
218 if (index < 0) {
219 continue;
220 }
221 final SimContact contact = contacts.get(index);
222 final long id = DataQuery.getRawContactId(dataCursor);
223 if (!rawContactToSimContact.containsKey(id)) {
224 rawContactToSimContact.put(id, new ArrayList<SimContact>());
225 }
226 rawContactToSimContact.get(id).add(contact);
227 }
228 } finally {
229 dataCursor.close();
230 }
231
232 final Cursor accountsCursor = queryAccountsOfRawContacts(rawContactToSimContact.keySet());
233 try {
234 while (accountsCursor.moveToNext()) {
235 final AccountWithDataSet account = AccountQuery.getAccount(accountsCursor);
236 final long id = AccountQuery.getId(accountsCursor);
237 if (!result.containsKey(account)) {
238 result.put(account, new ArraySet<SimContact>());
239 }
240 for (SimContact contact : rawContactToSimContact.get(id)) {
241 result.get(account).add(contact);
242 }
243 }
244 } finally {
245 accountsCursor.close();
246 }
247 }
248
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700249 private ContentProviderResult[] importBatch(List<SimContact> contacts,
250 AccountWithDataSet targetAccount)
251 throws RemoteException, OperationApplicationException {
252 final ArrayList<ContentProviderOperation> ops =
253 createImportOperations(contacts, targetAccount);
254 return mResolver.applyBatch(ContactsContract.AUTHORITY, ops);
255 }
256
257 @TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
258 private List<SimCard> getSimCardsFromSubscriptions() {
259 final SubscriptionManager subscriptionManager = (SubscriptionManager)
260 mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
261 final List<SubscriptionInfo> subscriptions = subscriptionManager
262 .getActiveSubscriptionInfoList();
263 final ArrayList<SimCard> result = new ArrayList<>();
264 for (SubscriptionInfo subscriptionInfo : subscriptions) {
265 result.add(SimCard.create(subscriptionInfo));
266 }
267 return result;
268 }
269
270 private List<SimContact> getContactsForSim(SimCard sim) {
271 final List<SimContact> contacts = sim.getContacts();
272 return contacts != null ? contacts : loadContactsForSim(sim);
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700273 }
274
Marcus Hagerottcf5d2612016-11-11 16:14:35 -0800275 // See b/32831092
276 // Sometimes the SIM contacts provider seems to get stuck if read from multiple threads
277 // concurrently. So we just have a global lock around it to prevent potential issues.
278 private static final Object SIM_READ_LOCK = new Object();
Marcus Hagerott819214d2016-09-29 14:58:27 -0700279 private ArrayList<SimContact> loadFrom(Uri uri) {
Marcus Hagerottcf5d2612016-11-11 16:14:35 -0800280 synchronized (SIM_READ_LOCK) {
281 final Cursor cursor = mResolver.query(uri, null, null, null, null);
Marcus Hagerott819214d2016-09-29 14:58:27 -0700282
Marcus Hagerottcf5d2612016-11-11 16:14:35 -0800283 try {
284 return loadFromCursor(cursor);
285 } finally {
286 cursor.close();
287 }
Marcus Hagerott819214d2016-09-29 14:58:27 -0700288 }
289 }
290
291 private ArrayList<SimContact> loadFromCursor(Cursor cursor) {
292 final int colId = cursor.getColumnIndex(_ID);
293 final int colName = cursor.getColumnIndex(NAME);
294 final int colNumber = cursor.getColumnIndex(NUMBER);
295 final int colEmails = cursor.getColumnIndex(EMAILS);
296
297 final ArrayList<SimContact> result = new ArrayList<>();
298
299 while (cursor.moveToNext()) {
300 final long id = cursor.getLong(colId);
301 final String name = cursor.getString(colName);
302 final String number = cursor.getString(colNumber);
303 final String emails = cursor.getString(colEmails);
304
305 final SimContact contact = new SimContact(id, name, number, parseEmails(emails));
306 result.add(contact);
307 }
308 return result;
309 }
310
Marcus Hagerott6c42b4c2016-10-31 14:59:53 -0700311 private Cursor queryRawContactsForSimContacts(List<SimContact> contacts) {
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700312 final StringBuilder selectionBuilder = new StringBuilder();
313
314 int phoneCount = 0;
Marcus Hagerott19d7eca2016-11-21 16:05:31 -0800315 int nameCount = 0;
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700316 for (SimContact contact : contacts) {
317 if (contact.hasPhone()) {
318 phoneCount++;
Marcus Hagerott19d7eca2016-11-21 16:05:31 -0800319 } else if (contact.hasName()) {
320 nameCount++;
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700321 }
322 }
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700323 List<String> selectionArgs = new ArrayList<>(phoneCount + 1);
324
Marcus Hagerott19d7eca2016-11-21 16:05:31 -0800325 selectionBuilder.append('(');
326 selectionBuilder.append(Data.MIMETYPE).append("=? AND ");
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700327 selectionArgs.add(Phone.CONTENT_ITEM_TYPE);
328
329 selectionBuilder.append(Phone.NUMBER).append(" IN (")
330 .append(Joiner.on(',').join(Collections.nCopies(phoneCount, '?')))
Marcus Hagerott19d7eca2016-11-21 16:05:31 -0800331 .append(')');
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700332 for (SimContact contact : contacts) {
333 if (contact.hasPhone()) {
334 selectionArgs.add(contact.getPhone());
335 }
336 }
Marcus Hagerott19d7eca2016-11-21 16:05:31 -0800337 selectionBuilder.append(')');
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700338
Marcus Hagerott19d7eca2016-11-21 16:05:31 -0800339 if (nameCount > 0) {
340 selectionBuilder.append(" OR (");
341
342 selectionBuilder.append(Data.MIMETYPE).append("=? AND ");
343 selectionArgs.add(StructuredName.CONTENT_ITEM_TYPE);
344
345 selectionBuilder.append(Data.DISPLAY_NAME).append(" IN (")
346 .append(Joiner.on(',').join(Collections.nCopies(nameCount, '?')))
347 .append(')');
348 for (SimContact contact : contacts) {
349 if (!contact.hasPhone() && contact.hasName()) {
350 selectionArgs.add(contact.getName());
351 }
352 }
Marcus Hagerott2b2cbe92016-11-22 10:54:17 -0800353 selectionBuilder.append(')');
Marcus Hagerott19d7eca2016-11-21 16:05:31 -0800354 }
Marcus Hagerott19d7eca2016-11-21 16:05:31 -0800355
356 return mResolver.query(Data.CONTENT_URI.buildUpon()
357 .appendQueryParameter(Data.VISIBLE_CONTACTS_ONLY, "true")
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700358 .build(),
359 DataQuery.PROJECTION,
360 selectionBuilder.toString(),
361 selectionArgs.toArray(new String[selectionArgs.size()]),
Marcus Hagerott6c42b4c2016-10-31 14:59:53 -0700362 null);
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700363 }
364
Marcus Hagerott6c42b4c2016-10-31 14:59:53 -0700365 private Cursor queryAccountsOfRawContacts(Set<Long> ids) {
366 final StringBuilder selectionBuilder = new StringBuilder();
367
368 final String[] args = new String[ids.size()];
369
370 selectionBuilder.append(RawContacts._ID).append(" IN (")
371 .append(Joiner.on(',').join(Collections.nCopies(args.length, '?')))
372 .append(")");
373 int i = 0;
374 for (long id : ids) {
375 args[i++] = String.valueOf(id);
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700376 }
Marcus Hagerott6c42b4c2016-10-31 14:59:53 -0700377 return mResolver.query(RawContacts.CONTENT_URI,
378 AccountQuery.PROJECTION,
379 selectionBuilder.toString(),
380 args,
381 null);
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700382 }
383
Marcus Hagerott819214d2016-09-29 14:58:27 -0700384 private ArrayList<ContentProviderOperation> createImportOperations(List<SimContact> contacts,
385 AccountWithDataSet targetAccount) {
386 final ArrayList<ContentProviderOperation> ops = new ArrayList<>();
387 for (SimContact contact : contacts) {
388 contact.appendCreateContactOperations(ops, targetAccount);
389 }
390 return ops;
391 }
392
393 private String[] parseEmails(String emails) {
394 return emails != null ? emails.split(",") : null;
395 }
John Shao3ed3af22016-10-18 18:31:29 -0700396
Marcus Hagerott2ea50f12016-10-21 09:30:24 -0700397 private boolean hasTelephony() {
398 return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
399 }
400
401 private boolean hasPermissions() {
402 return PermissionsUtil.hasContactsPermissions(mContext) &&
403 PermissionsUtil.hasPhonePermissions(mContext);
404 }
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700405
406 public static SimContactDao create(Context context) {
407 if (USE_FAKE_INSTANCE) {
408 return new DebugImpl(context)
Marcus Hagerott7333c372016-11-07 09:40:20 -0800409 .addSimCard(new SimCard("fake-sim-id1", 1, "Fake Carrier",
410 "Card 1", "15095550101", "us").withContacts(
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700411 new SimContact(1, "Sim One", "15095550111", null),
412 new SimContact(2, "Sim Two", "15095550112", null),
413 new SimContact(3, "Sim Three", "15095550113", null),
Marcus Hagerott216e2972016-10-26 15:53:42 -0700414 new SimContact(4, "Sim Four", "15095550114", null),
415 new SimContact(5, "411 & more", "411", null)
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700416 ))
Marcus Hagerott7333c372016-11-07 09:40:20 -0800417 .addSimCard(new SimCard("fake-sim-id2", 2, "Carrier Two",
418 "Card 2", "15095550102", "us").withContacts(
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700419 new SimContact(1, "John Sim", "15095550121", null),
420 new SimContact(2, "Bob Sim", "15095550122", null),
421 new SimContact(3, "Mary Sim", "15095550123", null),
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700422 new SimContact(4, "Alice Sim", "15095550124", null),
423 new SimContact(5, "Sim Duplicate", "15095550121", null)
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700424 ));
425 }
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700426 return new SimContactDao(context);
427 }
428
429 // TODO remove this class and the USE_FAKE_INSTANCE flag once this code is not under
430 // active development or anytime after 3/1/2017
Marcus Hagerott7333c372016-11-07 09:40:20 -0800431 public static class DebugImpl extends SimContactDao {
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700432
433 private List<SimCard> mSimCards = new ArrayList<>();
434 private SparseArray<SimCard> mCardsBySubscription = new SparseArray<>();
435
436 public DebugImpl(Context context) {
437 super(context);
438 }
439
440 public DebugImpl addSimCard(SimCard sim) {
441 mSimCards.add(sim);
442 mCardsBySubscription.put(sim.getSubscriptionId(), sim);
443 return this;
444 }
445
446 @Override
Marcus Hagerott66e8b222016-10-23 15:41:55 -0700447 public List<SimCard> getSimCards() {
448 return SharedPreferenceUtil.restoreSimStates(getContext(), mSimCards);
449 }
450
451 @Override
452 public ArrayList<SimContact> loadSimContacts() {
453 return new ArrayList<>(mSimCards.get(0).getContacts());
454 }
455
456 @Override
457 public ArrayList<SimContact> loadSimContacts(int subscriptionId) {
458 return new ArrayList<>(mCardsBySubscription.get(subscriptionId).getContacts());
459 }
460
461 @Override
462 public boolean canReadSimContacts() {
463 return true;
464 }
465 }
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700466
467 // Query used for detecting existing contacts that may match a SimContact.
468 private static final class DataQuery {
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700469
470 public static final String[] PROJECTION = new String[] {
Marcus Hagerott19d7eca2016-11-21 16:05:31 -0800471 Data.RAW_CONTACT_ID, Phone.NUMBER, Data.DISPLAY_NAME, Data.MIMETYPE
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700472 };
473
474 public static final int RAW_CONTACT_ID = 0;
475 public static final int PHONE_NUMBER = 1;
476 public static final int DISPLAY_NAME = 2;
Marcus Hagerott19d7eca2016-11-21 16:05:31 -0800477 public static final int MIMETYPE = 3;
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700478
479 public static long getRawContactId(Cursor cursor) {
480 return cursor.getLong(RAW_CONTACT_ID);
481 }
482
483 public static String getPhoneNumber(Cursor cursor) {
Marcus Hagerott19d7eca2016-11-21 16:05:31 -0800484 return isPhoneNumber(cursor) ? cursor.getString(PHONE_NUMBER) : null;
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700485 }
486
487 public static String getDisplayName(Cursor cursor) {
488 return cursor.getString(DISPLAY_NAME);
489 }
Marcus Hagerott19d7eca2016-11-21 16:05:31 -0800490
491 public static boolean isPhoneNumber(Cursor cursor) {
492 return Phone.CONTENT_ITEM_TYPE.equals(cursor.getString(MIMETYPE));
493 }
Marcus Hagerott2aa31982016-10-25 14:36:25 -0700494 }
Marcus Hagerott6c42b4c2016-10-31 14:59:53 -0700495
496 private static final class AccountQuery {
497 public static final String[] PROJECTION = new String[] {
498 RawContacts._ID, RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE,
499 RawContacts.DATA_SET
500 };
501
502 public static long getId(Cursor cursor) {
503 return cursor.getLong(0);
504 }
505
506 public static AccountWithDataSet getAccount(Cursor cursor) {
507 return new AccountWithDataSet(cursor.getString(1), cursor.getString(2),
508 cursor.getString(3));
509 }
510 }
Marcus Hagerott819214d2016-09-29 14:58:27 -0700511}