blob: 307577de528715fe3f6267f7515e680c751a1d7c [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 */
Gary Mai69c182a2016-12-05 13:07:03 -080016package com.android.contacts.model;
Marcus Hagerott7a756ab2016-11-01 18:16:02 -070017
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
Gary Mai69c182a2016-12-05 13:07:03 -080025import com.android.contacts.model.account.AccountWithDataSet;
26import com.android.contacts.util.DeviceLocalAccountTypeFactory;
Marcus Hagerott7a756ab2016-11-01 18:16:02 -070027
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,
Marcus Hagerottc2093f32016-12-12 10:18:12 -080063 Set<String> knownAccountTypes) {
Marcus Hagerott7a756ab2016-11-01 18:16:02 -070064 mResolver = contentResolver;
65 mAccountTypeFactory = factory;
66
Marcus Hagerott7a756ab2016-11-01 18:16:02 -070067 mSelection = getSelection(knownAccountTypes);
68 mSelectionArgs = getSelectionArgs(knownAccountTypes);
69 }
70
71 @Override
72 public List<AccountWithDataSet> getDeviceLocalAccounts() {
73
74 final Set<AccountWithDataSet> localAccounts = new HashSet<>();
75
76 // Many device accounts have default groups associated with them.
77 addAccountsFromQuery(ContactsContract.Groups.CONTENT_URI, localAccounts);
78 addAccountsFromQuery(ContactsContract.Settings.CONTENT_URI, localAccounts);
79 addAccountsFromQuery(ContactsContract.RawContacts.CONTENT_URI, localAccounts);
80
81 return new ArrayList<>(localAccounts);
82 }
83
84 private void addAccountsFromQuery(Uri uri, Set<AccountWithDataSet> accounts) {
85 final Cursor cursor = mResolver.query(uri, PROJECTION, mSelection, mSelectionArgs, null);
86
87 if (cursor == null) return;
88
89 try {
90 addAccountsFromCursor(cursor, accounts);
91 } finally {
92 cursor.close();
93 }
94 }
95
96 private void addAccountsFromCursor(Cursor cursor, Set<AccountWithDataSet> accounts) {
97 while (cursor.moveToNext()) {
98 final String name = cursor.getString(COL_NAME);
99 final String type = cursor.getString(COL_TYPE);
100 final String dataSet = cursor.getString(COL_DATA_SET);
101
102 if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
103 mAccountTypeFactory, type)) {
104 accounts.add(new AccountWithDataSet(name, type, dataSet));
105 }
106 }
107 }
108
109 @VisibleForTesting
110 public String getSelection() {
111 return mSelection;
112 }
113
114 @VisibleForTesting
115 public String[] getSelectionArgs() {
116 return mSelectionArgs;
117 }
118
119 private static String getSelection(Set<String> knownAccountTypes) {
120 final StringBuilder sb = new StringBuilder()
121 .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL");
122 if (knownAccountTypes.isEmpty()) {
123 return sb.toString();
124 }
125 sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN (");
126 for (String ignored : knownAccountTypes) {
127 sb.append("?,");
128 }
129 // Remove trailing ','
130 sb.deleteCharAt(sb.length() - 1).append(')');
131 return sb.toString();
132 }
133
134 private static String[] getSelectionArgs(Set<String> knownAccountTypes) {
135 if (knownAccountTypes.isEmpty()) return null;
136
137 return knownAccountTypes.toArray(new String[knownAccountTypes.size()]);
138 }
139}