blob: 64f7c039423739705d39df700b7901b500bb11d1 [file] [log] [blame]
Marcus Hagerott7a756ab2016-11-01 18:16:02 -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.model;
17
18import android.accounts.AccountManager;
19import android.content.ContentResolver;
20import android.database.Cursor;
21import android.net.Uri;
22import android.provider.ContactsContract;
23import android.support.annotation.VisibleForTesting;
24
25import com.android.contacts.common.model.account.AccountWithDataSet;
26import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
27
28import java.util.ArrayList;
29import java.util.HashSet;
30import java.util.List;
31import java.util.Set;
32
33/**
34 * Attempts to create accounts for "Device" contacts by querying
35 * CP2 for records with {@link android.provider.ContactsContract.RawContacts#ACCOUNT_TYPE} columns
36 * that do not exist for any account returned by {@link AccountManager#getAccounts()}
37 *
38 * This class should be used from a background thread since it does DB queries
39 */
40public class Cp2DeviceLocalAccountLocator extends DeviceLocalAccountLocator {
41
42 // Note this class is assuming ACCOUNT_NAME and ACCOUNT_TYPE have same values in
43 // RawContacts, Groups, and Settings. This assumption simplifies the code somewhat and it
44 // is true right now and unlikely to ever change.
45 @VisibleForTesting
46 static String[] PROJECTION = new String[] {
47 ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE,
48 ContactsContract.RawContacts.DATA_SET
49 };
50
51 private static final int COL_NAME = 0;
52 private static final int COL_TYPE = 1;
53 private static final int COL_DATA_SET = 2;
54
55 private final ContentResolver mResolver;
56 private final DeviceLocalAccountTypeFactory mAccountTypeFactory;
57
58 private final String mSelection;
59 private final String[] mSelectionArgs;
60
61 public Cp2DeviceLocalAccountLocator(ContentResolver contentResolver,
62 DeviceLocalAccountTypeFactory factory,
63 List<AccountWithDataSet> knownAccounts) {
64 mResolver = contentResolver;
65 mAccountTypeFactory = factory;
66
67 final Set<String> knownAccountTypes = new HashSet<>();
68 for (AccountWithDataSet account : knownAccounts) {
69 knownAccountTypes.add(account.type);
70 }
71 mSelection = getSelection(knownAccountTypes);
72 mSelectionArgs = getSelectionArgs(knownAccountTypes);
73 }
74
75 @Override
76 public List<AccountWithDataSet> getDeviceLocalAccounts() {
77
78 final Set<AccountWithDataSet> localAccounts = new HashSet<>();
79
80 // Many device accounts have default groups associated with them.
81 addAccountsFromQuery(ContactsContract.Groups.CONTENT_URI, localAccounts);
82 addAccountsFromQuery(ContactsContract.Settings.CONTENT_URI, localAccounts);
83 addAccountsFromQuery(ContactsContract.RawContacts.CONTENT_URI, localAccounts);
84
85 return new ArrayList<>(localAccounts);
86 }
87
88 private void addAccountsFromQuery(Uri uri, Set<AccountWithDataSet> accounts) {
89 final Cursor cursor = mResolver.query(uri, PROJECTION, mSelection, mSelectionArgs, null);
90
91 if (cursor == null) return;
92
93 try {
94 addAccountsFromCursor(cursor, accounts);
95 } finally {
96 cursor.close();
97 }
98 }
99
100 private void addAccountsFromCursor(Cursor cursor, Set<AccountWithDataSet> accounts) {
101 while (cursor.moveToNext()) {
102 final String name = cursor.getString(COL_NAME);
103 final String type = cursor.getString(COL_TYPE);
104 final String dataSet = cursor.getString(COL_DATA_SET);
105
106 if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
107 mAccountTypeFactory, type)) {
108 accounts.add(new AccountWithDataSet(name, type, dataSet));
109 }
110 }
111 }
112
113 @VisibleForTesting
114 public String getSelection() {
115 return mSelection;
116 }
117
118 @VisibleForTesting
119 public String[] getSelectionArgs() {
120 return mSelectionArgs;
121 }
122
123 private static String getSelection(Set<String> knownAccountTypes) {
124 final StringBuilder sb = new StringBuilder()
125 .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL");
126 if (knownAccountTypes.isEmpty()) {
127 return sb.toString();
128 }
129 sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN (");
130 for (String ignored : knownAccountTypes) {
131 sb.append("?,");
132 }
133 // Remove trailing ','
134 sb.deleteCharAt(sb.length() - 1).append(')');
135 return sb.toString();
136 }
137
138 private static String[] getSelectionArgs(Set<String> knownAccountTypes) {
139 if (knownAccountTypes.isEmpty()) return null;
140
141 return knownAccountTypes.toArray(new String[knownAccountTypes.size()]);
142 }
143}