/* * Funambol is a mobile platform developed by Funambol, Inc. * Copyright (C) 2010 Funambol, Inc. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. * * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License * version 3, these Appropriate Legal Notices must retain the display of the * "Powered by Funambol" logo. If the display of the logo is not reasonably * feasible for technical reasons, the Appropriate Legal Notices must display * the words "Powered by Funambol". */ package de.chbosync.android.syncmlclient; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; import java.util.Set; import java.util.Vector; import android.accounts.Account; import android.accounts.AccountManager; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.SyncAdapterType; import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.RawContacts; import com.funambol.client.configuration.Configuration; import com.funambol.util.Log; import com.funambol.util.StringUtil; import de.chbosync.android.syncmlclient.R; import de.chbosync.android.syncmlclient.controller.AndroidController; /** * This class manages the import of items owned by accounts that are created * externally from the Funambol application into the Funambol account. * * It is possible also to show/hide those items which have been previously * imported. */ public class ExternalAccountManager { private static final String TAG_LOG = "ExternalAccountManager"; private static final String CONF_KEY_IMPORTED_ACCOUNTS = "CONF_KEY_IMPORTED_ACCOUNTS"; private static final String CONF_KEY_ACCOUNTS_IMPORTED = "CONF_KEY_ACCOUNTS_IMPORTED"; private static final String CONF_KEY_PHONE_ONLY_IMPORTED = "CONF_KEY_PHONE_ONLY_IMPORTED"; private static final String CONF_KEY_SIM_IMPORTED = "CONF_KEY_SIM_IMPORTED"; private static final String CONF_KEY_HIDDEN_GROUPS = "CONF_KEY_HIDDEN_GROUPS"; private static final String CONF_KEY_HIDDEN_SETTINGS = "CONF_KEY_HIDDEN_SETTINGS"; private static final int CONTACTS_BATCH_COUNT = 10; private static final long UNDEFINED_CONTACT_ID = -1; private static ExternalAccountManager instance = null; private static final Uri SIM_CONTENT_URI = Uri.parse("content://icc/adn"); private Context context = null; private ContentResolver resolver = null; private Configuration configuration = null; private Account targetAccount = null; private static final String[] DATA_PROJECTION = { Data.MIMETYPE, Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, Data.DATA5, Data.DATA6, Data.DATA7, Data.DATA8, Data.DATA9, Data.DATA10, Data.DATA11, Data.DATA12, Data.DATA13, Data.DATA14, Data.DATA15, Data.IS_PRIMARY, Data.IS_SUPER_PRIMARY }; private static final String[] RAW_CONTACTS_PROJECTION = { RawContacts._ID, RawContacts.CONTACT_ID }; private ExternalAccountManager(Context context) { this.context = context; this.resolver = context.getContentResolver(); this.configuration = AndroidController.getInstance().getConfiguration(); this.targetAccount = AndroidAccountManager.getNativeAccount(context); } /** * Singleton implementation * * @param c The application Context * @return The single instance of this class */ public static synchronized ExternalAccountManager getInstance(Context c) { if (instance == null) { instance = new ExternalAccountManager(c); } return instance; } /** * Used by importAccountItems to notify of the import progress */ public interface ItemsImportListener { /** * The total amount of items has been updated * @param count */ public void setTotalItemsCount(int count); /** * The amount of imported items has been update * @param count */ public void updateImportedItemsCount(int count); } /** * Get the list of contacts currently stored on the SIM. */ public Cursor getSimContacts() { Cursor cursorSim = resolver.query(SIM_CONTENT_URI, null, null, null, null); return cursorSim; } /** * Import the items belonging to the given accounts to Funambol. Only * Contact items are supported. * * @param accounts The accounts from which the items shall be imported. * @param includePhoneOnly Include also items which don't belong to any * account. * @param includeSim Include also SIM items * @param listener The progress status listener * * @return The amount of imported items. -1 in case of errors */ public int importAccountItems(Vector<Account> accounts, boolean includePhoneOnly, boolean includeSim, ItemsImportListener listener) { int simImportedDirectly = 0; int simItemsCount = 0; boolean importSimAlone = false; // Initialize the total number of items to zero. As soon as the actual // number will be available, the UI will be updated if(listener != null) { listener.setTotalItemsCount(0); } // First of all we must compute the total number of items to import if (includeSim) { simItemsCount = getSimItemsCount(); if (simItemsCount > 0 && !hasSimItemsInContactsProvider()) { importSimAlone = true; includeSim = false; } } Log.trace(TAG_LOG, "importAccountItems"); // Keep track of the catched excptions Exception lastException = null; // Update the target account this.targetAccount = AndroidAccountManager.getNativeAccount(context); // // Build the where clause for the raw contacts selection query // StringBuffer whereClause = new StringBuffer(); // Includes the given accounts includeAccountsToSelection(whereClause, accounts); saveImportedAccounts(accounts); // Includes phone only items if needed if(includePhoneOnly) { includePhoneOnlyItemsToSelection(whereClause); savePhoneOnlyImported(); } // Includes sim items if needed if(includeSim) { includeSimItemsToSelection(whereClause); saveSimImported(); } // Excludes Funambol items excludeFunambolItemsToSelection(whereClause); String selection = whereClause.toString(); Log.trace(TAG_LOG, "Query selection: " + selection); if(selection.length() == 0 && !importSimAlone) { // No items to import Log.info(TAG_LOG, "No items to import"); return 0; } Cursor rawContactsCursor = null; int totalItemsCount = simItemsCount; if (selection.length() > 0) { // Queries the RawContacts table to find all the entries associated // with the given accounts. rawContactsCursor = resolver.query(RawContacts.CONTENT_URI, RAW_CONTACTS_PROJECTION, selection, null, null); totalItemsCount += rawContactsCursor.getCount(); } if(listener != null) { listener.setTotalItemsCount(totalItemsCount); } // First of all we import all the SIM contacts if they need to be // imported separately if (importSimAlone) { // The SIM card has items, but these items are not exposed via // the ContactsProvider. In this case we import from the SIM // directly. This can generate more duplicates but there is no // much else we can do Log.info(TAG_LOG, "The contacts provider does not expose SIM contacts, import directly"); simImportedDirectly = importSimItems(null, listener, false); } // Now import everything else (this may include SIM contacts if these // contacts are exposed by the ContactsProvider) // Holds the contacts which have been already added to the target account Hashtable<Long, Long> contactIds = new Hashtable<Long, Long>(); int importedItemsCount = simImportedDirectly; // Add to the contactIds all the contact ids which already exist in the // target account. This is to avoid contacts duplication. Cursor targetAccountContactsCursor = resolver.query(RawContacts.CONTENT_URI, RAW_CONTACTS_PROJECTION, RawContacts.ACCOUNT_TYPE+"='"+targetAccount.type+"' AND "+ RawContacts.ACCOUNT_NAME+"='"+targetAccount.name+"'", null, null); if(targetAccountContactsCursor != null && targetAccountContactsCursor.moveToFirst()) { do { contactIds.put(targetAccountContactsCursor.getLong(1), targetAccountContactsCursor.getLong(0)); } while(targetAccountContactsCursor.moveToNext()); } if(targetAccountContactsCursor != null) { targetAccountContactsCursor.close(); } // Collects all the operations to perform in order to import a // single contact ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); int contactsBatchCount = 0; int tempReferenceId = 0; Hashtable<Long, Integer> backReferenceIds = new Hashtable<Long, Integer>(); try { if(rawContactsCursor != null && rawContactsCursor.moveToFirst()) { do { long exRawContactId = rawContactsCursor.getLong(0); long contactId = rawContactsCursor.getLong(1); Log.trace(TAG_LOG, "Contact id: " + contactId); if(contactId <= 0) { Log.trace(TAG_LOG, "Invalid contact id: " + contactId); totalItemsCount--; if(listener != null) { listener.setTotalItemsCount(totalItemsCount); } continue; } long newRowContactId = UNDEFINED_CONTACT_ID; // Check if this contact is aggregated boolean isAggregatedContact = contactIds.containsKey(contactId); if(isAggregatedContact) { Log.trace(TAG_LOG, "Aggregated contact found"); newRowContactId = contactIds.get(contactId); } else { Log.trace(TAG_LOG, "New contact found"); insertNewRawContact(ops); // Keep track of the back reference id to be used while // adding new data records backReferenceIds.put(contactId, tempReferenceId); tempReferenceId++; // Put an undefined contact id. It will be stored once // returned by applyBatch contactIds.put(contactId, UNDEFINED_CONTACT_ID); } // Read the existing contact data whereClause = new StringBuffer(); whereClause.append(Data.RAW_CONTACT_ID).append("=") .append(exRawContactId); Cursor dataCursor = resolver.query(Data.CONTENT_URI, DATA_PROJECTION, whereClause.toString(), null, null); try { if(dataCursor.moveToFirst()) { do { Integer id = backReferenceIds.get(contactId); insertNewDataRecord(dataCursor, newRowContactId, id, ops); tempReferenceId++; } while(dataCursor.moveToNext()); } } finally { dataCursor.close(); } contactsBatchCount++; importedItemsCount++; if(listener != null) { listener.updateImportedItemsCount(importedItemsCount); } if(contactsBatchCount == CONTACTS_BATCH_COUNT || rawContactsCursor.isLast()) { try { ContentProviderResult[] res = resolver.applyBatch( ContactsContract.AUTHORITY, ops); // Get the new created contact ids using the back // reference ids if(!backReferenceIds.isEmpty()) { Set<Long> keys = backReferenceIds.keySet(); for(long key : keys) { int backReference = backReferenceIds.get(key); long id = ContentUris.parseId(res[backReference].uri); contactIds.put(key, id); } } } catch(Exception ex) { Log.error(TAG_LOG, "Failed to import contacts batch", ex); lastException = ex; } finally { tempReferenceId = 0; contactsBatchCount = 0; backReferenceIds.clear(); ops.clear(); } } } while(rawContactsCursor.moveToNext()); } } catch(Exception ex) { Log.error(TAG_LOG, "Exception in importAccountItems", ex); lastException = ex; } finally { Log.info(TAG_LOG, "Imported contacts count: " + importedItemsCount); if (rawContactsCursor != null) { rawContactsCursor.close(); } } // Return -1 if something went wrong if(lastException != null) { return -1; } else { return importedItemsCount; } } /** * Import sim items only. This method allows to import SIM contacts into the * application account. It is also possible to select which contacts to * import, by providing the list of their ids. * * @param selected a set of contacts to be imported (by their SIM contact * key) * * @param listener the progress status listener. */ public int importSimItems(Set<Long> selected, ItemsImportListener listener) { return importSimItems(selected, listener, true); } /** * Import the items belonging to the given accounts to Funambol. Only * Contact items are supported. * * @param selected the list of selected contacts (null to import all of * them) * @param listener The progress status listener * * @return The amount of imported items. -1 in case of errors */ public int importSimItems(Set<Long> selected, ItemsImportListener listener, boolean notifyTotal) { Log.trace(TAG_LOG, "importSimItems"); // Keep track of the caught excptions Exception lastException = null; // Update the target account this.targetAccount = AndroidAccountManager.getNativeAccount(context); Cursor simContacts = getSimContacts(); int totalItemsCount = simContacts.getCount(); if(notifyTotal && listener != null) { listener.setTotalItemsCount(totalItemsCount); } int importedItemsCount = 0; // Collects all the operations to perform in order to import a // single contact ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); int contactsBatchCount = 0; try { if(simContacts.moveToFirst()) { int backReferenceId = 0; do { long id = simContacts.getLong(simContacts.getColumnIndex("_id")); Log.trace(TAG_LOG, "New contact found with id " + id); if (selected != null && !selected.contains(new Long(id))) { Log.info(TAG_LOG, "Skipping contact from import " + id); } else { // This operation generates a back reference insertNewRawContact(ops); // This operation generates another backReference int numInserts = insertSIMNewDataRecord(simContacts, ops, new Integer(backReferenceId)); contactsBatchCount++; importedItemsCount++; backReferenceId += (numInserts + 1); } if(listener != null) { listener.updateImportedItemsCount(importedItemsCount); } if(contactsBatchCount == CONTACTS_BATCH_COUNT || simContacts.isLast()) { try { resolver.applyBatch(ContactsContract.AUTHORITY, ops); } catch(Exception ex) { Log.error(TAG_LOG, "Failed to import contacts batch", ex); lastException = ex; } finally { contactsBatchCount = 0; backReferenceId = 0; ops.clear(); } } } while(simContacts.moveToNext()); } } catch(Exception ex) { Log.error(TAG_LOG, "Exception in importAccountItems", ex); lastException = ex; } finally { Log.info(TAG_LOG, "Imported contacts count: " + importedItemsCount); simContacts.close(); } // Return -1 if something went wrong if(lastException != null) { return -1; } else { return importedItemsCount; } } private int insertSIMNewDataRecord(Cursor c, List<ContentProviderOperation> ops, Integer backReferenceId) { String dn = c.getString(c.getColumnIndex("name")); String number = c.getString(c.getColumnIndex("number")); Log.trace(TAG_LOG, "Found SIM contact with name: " + dn); Log.trace(TAG_LOG, "Found SIM contact with number: " + number); int numInserts = 0; if (StringUtil.isNullOrEmpty(dn) && StringUtil.isNullOrEmpty(number)) { return numInserts; } if (number != null) { ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); builder = builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, backReferenceId); builder = builder.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE); builder = builder.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, number); builder = builder.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_OTHER); ops.add(builder.build()); numInserts++; } // If the name is not available, we use the number as a name if (dn != null) { ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); builder = builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, backReferenceId); builder = builder.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE); builder = builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, dn); ops.add(builder.build()); numInserts++; } return numInserts; } private void includeAccountsToSelection(StringBuffer whereClause, Vector<Account> accounts) { Log.debug(TAG_LOG, "Include accounts"); for (int i = 0; i < accounts.size(); i++) { Account account = accounts.elementAt(i); Log.info(TAG_LOG, "Importing account: " + account); if (i != 0) { whereClause.append(" OR "); } String accountName = account.name; String accountType = account.type; whereClause .append("(") .append(RawContacts.ACCOUNT_NAME) .append("='").append(accountName).append("'") .append(" AND ") .append(RawContacts.ACCOUNT_TYPE) .append("='").append(accountType).append("'") .append(")"); } } private void includePhoneOnlyItemsToSelection(StringBuffer whereClause) { Log.debug(TAG_LOG, "Include phone only items"); // To include phone only contacts we have just to include contacts // which don't belong to any account Vector<Account> allAccounts = listContactAccounts(true, false); if(allAccounts.isEmpty()) { whereClause.append("(1)"); } boolean first = true; for(int i=0; i<allAccounts.size(); i++) { Account a = allAccounts.elementAt(i); String accountName = a.name; String accountType = a.type; if(first) { if(whereClause.length() > 0) { whereClause.append(" OR "); } whereClause.append("("); first = false; } else { whereClause.append(" AND "); } whereClause .append("(") .append(RawContacts.ACCOUNT_NAME) .append("!='").append(accountName).append("'") .append(" AND ") .append(RawContacts.ACCOUNT_TYPE) .append("!='").append(accountType).append("'") .append(")"); } if(!first) { whereClause.append(")"); // Exclude sim contacts whereClause .append(" AND (") .append(RawContacts.ACCOUNT_TYPE) .append(" NOT LIKE '%.sim%')"); } } private void includeSimItemsToSelection(StringBuffer whereClause) { Log.debug(TAG_LOG, "Include SIM items"); if(whereClause.length() > 0) { whereClause.append(" OR "); } whereClause .append("(") .append(RawContacts.ACCOUNT_TYPE) .append(" LIKE '%.sim%')"); } private void excludeFunambolItemsToSelection(StringBuffer whereClause) { // Make sure to not include funambol items String funType = context.getString(R.string.account_type); if(whereClause.length() > 0) { whereClause.append(" AND "); whereClause .append("(") .append(RawContacts.ACCOUNT_TYPE) .append("!='").append(funType).append("')"); } } /** * Returns the amount of items belonging to the given account * * @param account * @return */ public int getAccountItemsCount(Account account) { int count = 0; Cursor result = null; try { result = resolver.query(RawContacts.CONTENT_URI, null, RawContacts.ACCOUNT_NAME + "='" + account.name + "' AND " + RawContacts.ACCOUNT_TYPE + "='" + account.type+ "'", null, null); if(result != null) { count = result.getCount(); } } finally { if(result != null) { result.close(); } } return count; } /** * @return true if the provider actually contains SIM items */ public boolean hasSimItems() { return getSimItemsCount() > 0; } private int getSimItemsCount() { int num = 0; Cursor simCursor = getSimContacts(); if(simCursor != null) { num = simCursor.getCount(); simCursor.close(); } return num; } private boolean hasSimItemsInContactsProvider() { Cursor simCursor = resolver.query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID}, RawContacts.ACCOUNT_TYPE+" LIKE '%.sim%'", null, null); boolean result = false; if(simCursor != null) { result = simCursor.getCount() > 0; simCursor.close(); } return result; } /** * @return true if the provider actually contains phone only items */ public boolean hasPhoneOnlyItems() { StringBuffer whereClause = new StringBuffer(); includePhoneOnlyItemsToSelection(whereClause); excludeFunambolItemsToSelection(whereClause); Cursor phoneOnlyCursor = resolver.query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID}, whereClause.toString(), null, null); boolean result = false; if(phoneOnlyCursor != null) { result = phoneOnlyCursor.getCount() > 0; phoneOnlyCursor.close(); } return result; } /** * @return true if any account has been already imported */ public boolean accountsImported() { return configuration.loadBooleanKey(CONF_KEY_ACCOUNTS_IMPORTED, false); } /** * @return true if phone only items have been already imported */ public boolean phoneOnlyImported() { return configuration.loadBooleanKey(CONF_KEY_PHONE_ONLY_IMPORTED, false); } /** * @return true if the SIM items have been already imported */ public boolean simImported() { return configuration.loadBooleanKey(CONF_KEY_SIM_IMPORTED, false); } /** * Resets the configuration values */ public void reset() { configuration.saveBooleanKey(CONF_KEY_ACCOUNTS_IMPORTED, false); configuration.saveBooleanKey(CONF_KEY_PHONE_ONLY_IMPORTED, false); configuration.saveBooleanKey(CONF_KEY_SIM_IMPORTED, false); configuration.saveStringKey(CONF_KEY_IMPORTED_ACCOUNTS, ""); configuration.saveStringKey(CONF_KEY_HIDDEN_GROUPS, ""); configuration.saveStringKey(CONF_KEY_HIDDEN_SETTINGS, ""); configuration.save(); } /** * Adds a new ContentProviderOperation to the given operations list which * add a new contact Data record by importing the given cursor's data fields * * @param c The Cursor from which the data fields shall be imported * @param ops The List of operations */ private void insertNewDataRecord(Cursor c, long newRowContactId, Integer backReferenceId, List<ContentProviderOperation> ops) { ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); if(newRowContactId != UNDEFINED_CONTACT_ID) { builder = builder.withValue(ContactsContract.Data.RAW_CONTACT_ID, newRowContactId); } else { builder = builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, backReferenceId); } builder = builder.withValue(Data.MIMETYPE, c.getString(0)); builder = builder.withValue(Data.DATA1, c.getString(1)); builder = builder.withValue(Data.DATA2, c.getString(2)); builder = builder.withValue(Data.DATA3, c.getString(3)); builder = builder.withValue(Data.DATA4, c.getString(4)); builder = builder.withValue(Data.DATA5, c.getString(5)); builder = builder.withValue(Data.DATA6, c.getString(6)); builder = builder.withValue(Data.DATA7, c.getString(7)); builder = builder.withValue(Data.DATA8, c.getString(8)); builder = builder.withValue(Data.DATA9, c.getString(9)); builder = builder.withValue(Data.DATA10, c.getString(10)); builder = builder.withValue(Data.DATA11, c.getString(11)); builder = builder.withValue(Data.DATA12, c.getString(12)); builder = builder.withValue(Data.DATA13, c.getString(13)); builder = builder.withValue(Data.DATA14, c.getString(14)); builder = builder.withValue(Data.DATA15, c.getBlob(15)); builder = builder.withValue(Data.IS_PRIMARY, new Integer(c.getInt(16))); builder = builder.withValue(Data.IS_SUPER_PRIMARY, new Integer(c.getInt(17))); ops.add(builder.build()); } /** * Adds a new ContentProviderOperation to the given operations list which * inserts a new RawContact item. * * @param ops The List of operations */ private void insertNewRawContact(List<ContentProviderOperation> ops) { ContentProviderOperation i1 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) .withValue(RawContacts.ACCOUNT_NAME, targetAccount.name) .withValue(RawContacts.ACCOUNT_TYPE, targetAccount.type) .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT) .build(); ops.add(i1); } /** * Lists the accounts which support contact items. * * @param includeReadOnlyAccounts * @param excludeImportedAccounts * @return The Vector of the accounts */ public Vector<Account> listContactAccounts(boolean includeReadOnlyAccounts, boolean excludeImportedAccounts) { Log.trace(TAG_LOG, "listContactAccounts: " + includeReadOnlyAccounts); AccountManager am = AccountManager.get(context); Account[] accounts = am.getAccounts(); SyncAdapterType[] syncs = ContentResolver.getSyncAdapterTypes(); Vector<Account> contactAccounts = new Vector<Account>(); Vector<String> contactAccountTypes = new Vector<String>(); for (SyncAdapterType sync : syncs) { if (ContactsContract.AUTHORITY.equals(sync.authority) && (includeReadOnlyAccounts || sync.supportsUploading())) { contactAccountTypes.add(sync.accountType); } } String funType = context.getString(R.string.account_type); for (Account acct: accounts) { // Exclude Funambol accounts if (!funType.equals(acct.type) && contactAccountTypes.contains(acct.type)) { if(excludeImportedAccounts) { Vector<Account> importedAccounts = loadImportedAccounts(); boolean isImported = false; for(int i=0; i<importedAccounts.size(); i++) { Account account = importedAccounts.elementAt(i); if(account.name.equals(acct.name) && account.type.equals(acct.type)) { isImported = true; } } if(!isImported) { contactAccounts.add(acct); } } else { contactAccounts.add(acct); } } } return contactAccounts; } /** * Makes the previously imported accounts items (e.g. contacts) to be * hidden to the user. */ public void hideImportedAccounts() { Log.info(TAG_LOG, "Hiding imported accounts"); showHideImportedAccounts(false); } /** * Makes all the device's accounts items (e.g. contacts) to be hidden to the * user. */ public void hideAllAccounts() { Log.info(TAG_LOG, "Hiding all accounts"); showHideAllAccounts(false); } /** * Makes the accounts items (e.g. contacts) which have been hidden by the * hideImportedAccounts method, to be visible to the user. */ public void showHiddenAccounts() { Log.info(TAG_LOG, "Showing hidden accounts"); showHideImportedAccounts(true); } /** * Shows/Hides the imported accounts items depending on the show parameter. * Actually only contact items are supported. * * @param show */ private void showHideImportedAccounts(boolean show) { Vector<Account> accounts = new Vector<Account>(); if(!show) { accounts = loadImportedAccounts(); } showHideAccounts(accounts, show); } /** * Shows/Hides all the accounts items depending on the show parameter. * Actually only contact items are supported. * * @param show */ private void showHideAllAccounts(boolean show) { Vector<Account> accounts = new Vector<Account>(); if(!show) { accounts = listContactAccounts(true, false); } showHideAccounts(accounts, show); } /** * Shows/Hides the give accounts depending on the show parameter. * * @param accounts * @param show */ private void showHideAccounts(Vector<Account> accounts, boolean show) { showHideAccountGroups(accounts, show); showHideAccountSettings(accounts, show); } /** * Shows/Hides the contact groups belonging to the given accounts depending * on the show parameter. * * @param accounts * @param show */ private void showHideAccountGroups(Vector<Account> accounts, boolean show) { Log.trace(TAG_LOG, "showHideAccountGroups: " + show); String[] hiddenGroupIds; if(!show) { // If the settings shall be hidden we must retrieve the visible // groups for the given accounts. StringBuffer whereClause = new StringBuffer(); whereClause.append("("); for(int i=0; i<accounts.size(); i++) { Account account = accounts.elementAt(i); if(i != 0) { whereClause.append(" OR "); } whereClause .append("(") .append(ContactsContract.Groups.ACCOUNT_NAME) .append("='").append(account.name).append("'") .append(" AND ") .append(ContactsContract.Groups.ACCOUNT_TYPE) .append("='").append(account.type).append("'") .append(")"); } whereClause.append(")"); whereClause.append(" AND ") .append(ContactsContract.Groups.GROUP_VISIBLE).append("=1"); Cursor groups = resolver.query(ContactsContract.Groups.CONTENT_URI, new String[] { ContactsContract.Groups._ID }, whereClause.toString(), null, null); Vector<String> tempIds = new Vector<String>(); if(groups.moveToFirst()) { do { String id = groups.getString(0); Log.trace(TAG_LOG, "Found group to show/hide id: " + id); tempIds.add(id); } while(groups.moveToNext()); } groups.close(); hiddenGroupIds = new String[tempIds.size()]; tempIds.toArray(hiddenGroupIds); // Save the hidden groups into the configuration saveHiddenGroupIds(hiddenGroupIds); } else { // The hidden groups are already known by the configuration hiddenGroupIds = loadHiddenGroupIds(); } // Show/hide groups StringBuffer whereClause = new StringBuffer(); for(String id : hiddenGroupIds) { if(whereClause.length() > 0) { whereClause.append(" OR "); } Log.debug(TAG_LOG, "Setting visibility for group id: " + id); whereClause.append(ContactsContract.Groups._ID).append("=").append(id); } ContentValues cv = new ContentValues(); int visible = show ? 1 : 0; cv.put(ContactsContract.Groups.GROUP_VISIBLE, visible); resolver.update(ContactsContract.Groups.CONTENT_URI, cv, whereClause.toString(), null); } /** * Shows/Hides the contacts which doesn't belong to any account group. This * is performed by setting the UNGROUPED_VISIBLE field into the account * Settings table. * * See http://developer.android.com/reference/android/provider/ * ContactsContract.SettingsColumns.html#UNGROUPED_VISIBLE * * @param accounts * @param show */ private void showHideAccountSettings(Vector<Account> accounts, boolean show) { Log.trace(TAG_LOG, "showHideAccountSettings: " + show); Vector<Account> hiddenAccountSettings = new Vector(); if(!show) { // If the settings shall be hidden we must retrieve the visible // settings for the given accounts. StringBuffer whereClause = new StringBuffer(); whereClause.append("("); for(int i=0; i<accounts.size(); i++) { Account account = accounts.elementAt(i); if(i != 0) { whereClause.append(" OR "); } whereClause .append("(") .append(ContactsContract.Settings.ACCOUNT_NAME) .append("='").append(account.name).append("'") .append(" AND ") .append(ContactsContract.Settings.ACCOUNT_TYPE) .append("='").append(account.type).append("'") .append(")"); } whereClause.append(")"); whereClause.append(" AND ") .append(ContactsContract.Settings.UNGROUPED_VISIBLE).append("=1"); Cursor settings = resolver.query(ContactsContract.Settings.CONTENT_URI, new String[] { ContactsContract.Settings.ACCOUNT_NAME, ContactsContract.Settings.ACCOUNT_TYPE }, whereClause.toString(), null, null); if(settings.moveToFirst()) { do { String name = settings.getString(0); String type = settings.getString(1); Account account = new Account(name, type); Log.trace(TAG_LOG, "Found account to show/hide: " + account); hiddenAccountSettings.add(account); } while(settings.moveToNext()); } settings.close(); // Save the hidden settings into the configuration saveHiddenAccountSettings(hiddenAccountSettings); } else { // The hidden settings are already known by the configuration hiddenAccountSettings = loadHiddenAccountSettings(); } // Show/hide settings StringBuffer whereClause = new StringBuffer(); for(int i=0; i<hiddenAccountSettings.size(); i++) { Account account = hiddenAccountSettings.elementAt(i); Log.debug(TAG_LOG, "Setting visibility for account: " + account); if(i != 0) { whereClause.append(" OR "); } whereClause.append("("); whereClause.append(ContactsContract.Settings.ACCOUNT_NAME).append("='") .append(account.name).append("'"); whereClause.append(" AND "); whereClause.append(ContactsContract.Settings.ACCOUNT_TYPE).append("='") .append(account.type).append("'"); whereClause.append(")"); } ContentValues cv = new ContentValues(); int visible = show ? 1 : 0; cv.put(ContactsContract.Settings.UNGROUPED_VISIBLE, visible); resolver.update(ContactsContract.Settings.CONTENT_URI, cv, whereClause.toString(), null); } private String[] loadHiddenGroupIds() { Log.debug(TAG_LOG, "Loading hidden groups from the configuration"); String value = configuration.loadStringKey(CONF_KEY_HIDDEN_GROUPS, null); if(value != null) { return StringUtil.split(value, "\t"); } return new String[0]; } private Vector<Account> loadHiddenAccountSettings() { Log.debug(TAG_LOG, "Loading hidden account settings from the configuration"); return loadAccountsFromConfiguration(CONF_KEY_HIDDEN_SETTINGS); } private Vector<Account> loadImportedAccounts() { Log.debug(TAG_LOG, "Loading imported accounts from the configuration"); return loadAccountsFromConfiguration(CONF_KEY_IMPORTED_ACCOUNTS); } private Vector<Account> loadAccountsFromConfiguration(String confKey) { Log.trace(TAG_LOG, "loadAccountsFromConfiguration: " + confKey); Vector<Account> result = new Vector<Account>(); String accountsString = configuration.loadStringKey(CONF_KEY_IMPORTED_ACCOUNTS, null); if(StringUtil.isNullOrEmpty(accountsString)) { return result; } // Parse the accounts string String[] accountsStringArray = StringUtil.split(accountsString, "\n"); for(String accountString : accountsStringArray) { String[] accountInfo = StringUtil.split(accountString, "\t"); if(accountInfo != null && accountInfo.length == 2) { String name = accountInfo[0]; String type = accountInfo[1]; Account account = new Account(name, type); Log.debug(TAG_LOG, "Loading account: " + account); result.add(account); } else { Log.error(TAG_LOG, "Invalid account string: " + accountString); } } return result; } private void saveHiddenGroupIds(String[] ids) { Log.debug(TAG_LOG, "Saving hidden groups information"); String value = StringUtil.join(ids, "\t"); configuration.saveStringKey(CONF_KEY_HIDDEN_GROUPS, value); configuration.save(); } private void saveHiddenAccountSettings(Vector<Account> accounts) { Log.debug(TAG_LOG, "Saving hidden account settings information"); saveAccountsToConfiguration(CONF_KEY_HIDDEN_SETTINGS, accounts, false); } private void saveImportedAccounts(Vector<Account> accounts) { Log.debug(TAG_LOG, "Saving imported accounts information"); configuration.saveBooleanKey(CONF_KEY_ACCOUNTS_IMPORTED, true); saveAccountsToConfiguration(CONF_KEY_IMPORTED_ACCOUNTS, accounts, true); } private void savePhoneOnlyImported() { configuration.saveBooleanKey(CONF_KEY_PHONE_ONLY_IMPORTED, true); configuration.save(); } private void saveSimImported() { configuration.saveBooleanKey(CONF_KEY_SIM_IMPORTED, true); configuration.save(); } private void saveAccountsToConfiguration(String confKey, Vector<Account> accounts, boolean append) { Log.trace(TAG_LOG, "saveAccountsToConfiguration: " + confKey); StringBuffer toSave = new StringBuffer(); if(append && accountsImported()) { String existingData = configuration.loadStringKey(confKey, null); if(existingData != null) { toSave.append(existingData).append("\n"); } } for (int i = 0; i < accounts.size(); i++) { Account account = accounts.elementAt(i); Log.debug(TAG_LOG, "Saving account: " + account); if (i != 0) { toSave.append("\n"); } toSave.append(account.name).append("\t").append(account.type); } configuration.saveStringKey(confKey, toSave.toString()); configuration.save(); } }