1 /* 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 */ 16 package com.android.contacts.model; 17 18 import android.accounts.AccountManager; 19 import android.content.ContentResolver; 20 import android.database.Cursor; 21 import android.net.Uri; 22 import android.provider.ContactsContract; 23 import androidx.annotation.VisibleForTesting; 24 25 import com.android.contacts.model.account.AccountWithDataSet; 26 import com.android.contacts.util.DeviceLocalAccountTypeFactory; 27 28 import java.util.ArrayList; 29 import java.util.HashSet; 30 import java.util.List; 31 import 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 */ 40 public 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 Cp2DeviceLocalAccountLocator(ContentResolver contentResolver, DeviceLocalAccountTypeFactory factory, Set<String> knownAccountTypes)61 public Cp2DeviceLocalAccountLocator(ContentResolver contentResolver, 62 DeviceLocalAccountTypeFactory factory, 63 Set<String> knownAccountTypes) { 64 mResolver = contentResolver; 65 mAccountTypeFactory = factory; 66 67 mSelection = getSelection(knownAccountTypes); 68 mSelectionArgs = getSelectionArgs(knownAccountTypes); 69 } 70 71 @Override getDeviceLocalAccounts()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 addAccountsFromQuery(Uri uri, Set<AccountWithDataSet> accounts)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 addAccountsFromCursor(Cursor cursor, Set<AccountWithDataSet> accounts)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 getSelection()110 public String getSelection() { 111 return mSelection; 112 } 113 114 @VisibleForTesting getSelectionArgs()115 public String[] getSelectionArgs() { 116 return mSelectionArgs; 117 } 118 getSelection(Set<String> knownAccountTypes)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 getSelectionArgs(Set<String> knownAccountTypes)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 } 140