/* * 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.Vector; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AuthenticatorDescription; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.os.AsyncTask; import com.funambol.client.controller.Controller; import com.funambol.client.localization.Localization; import com.funambol.client.source.AppSyncSource; import com.funambol.client.source.AppSyncSourceManager; import com.funambol.client.ui.Screen; import com.funambol.util.Log; import com.funambol.util.StringUtil; import de.chbosync.android.syncmlclient.activities.AndroidDisplayManager; import de.chbosync.android.syncmlclient.controller.AndroidAdvancedSettingsScreenController; import de.chbosync.android.syncmlclient.controller.AndroidController; import de.chbosync.android.syncmlclient.source.pim.contact.ContactAppSyncSourceConfig; /** * This class is responsible of the contacts import functionality. The entry * point is the importContacts method which will popup a multiple selection * dialog in order to let the user to select the accounts to import. Once * selected and pressed the "Start Import" button the import contacts will run * through an ASyncTask which makes use of the ExternalAccountManager to import * contacts belonging to the selected accounts. */ public class ContactsImporter { private static final String TAG_LOG = "ContactsImporter"; private AndroidDisplayManager displayManager; private Localization localization; private Context context = null; private AndroidController controller = null; private int screenId; private AndroidAdvancedSettingsScreenController advSettController; public ContactsImporter(int screenId, Context context, AndroidAdvancedSettingsScreenController advSettController) { this.screenId = screenId; this.controller = AndroidController.getInstance(); this.context = context; this.localization = controller.getLocalization(); this.displayManager = (AndroidDisplayManager)controller.getDisplayManager(); this.advSettController = advSettController; } /** * The entry point for the import contacts process * * @param reset Resets the import configuration */ public void importContacts(boolean reset) { // Check screen validity if(getScreen() == null) { if (Log.isLoggable(Log.DEBUG)) { Log.debug(TAG_LOG, "Screen not yet initialized: " + screenId); } return; } // Init the external account manager used to handle contacts belonging // to other accounts final ExternalAccountManager aManager = ExternalAccountManager.getInstance(context); if(reset) { aManager.reset(); } // This is the first call of contacts import if there aren't any accounts // imported and the caller requested a reset final boolean isFirstImport = !aManager.accountsImported() && reset; // Prompt contacts import dialog ((Activity)getScreen().getUiScreen()).runOnUiThread(new Runnable() { public void run() { String title = null; if(isFirstImport) { title = localization.getLanguage("dialog_import_contacts_first"); } else { title = localization.getLanguage("dialog_import_contacts_later"); } String okButtonLabel = localization.getLanguage("dialog_start_import"); String cancelButtonLabel = localization.getLanguage("dialog_cancel_import"); Vector<Account> accounts = aManager.listContactAccounts(true, true); // Filter accounts which don't include any item for(int i=0; i<accounts.size(); i++) { Account account = accounts.elementAt(i); if(aManager.getAccountItemsCount(account) <= 0) { accounts.remove(account); i--; } } int choicesCount = accounts.size(); // Include Phone only contacts boolean showPhoneOnlyOption = aManager.hasPhoneOnlyItems() && !aManager.phoneOnlyImported(); int phoneOnlyOptionIndex = -1; if(showPhoneOnlyOption) { phoneOnlyOptionIndex = accounts.size(); choicesCount++; } // Include SIM contacts boolean showSimOption = aManager.hasSimItems() && !aManager.simImported(); int simOptionIndex = -1; if(showSimOption) { choicesCount++; if(!showPhoneOnlyOption) { simOptionIndex = accounts.size(); } else { simOptionIndex = accounts.size()+1; } } // Return in the case there are no address books to import if(choicesCount == 0) { if (Log.isLoggable(Log.DEBUG)) { Log.debug(TAG_LOG, "No address books to import"); } if(!isFirstImport) { displayManager.showOkDialog(getScreen(), localization.getLanguage("dialog_import_no_address_books"), localization.getLanguage("dialog_ok")); } return; } // Populate the import choices with the address books label String[] choices = new String[choicesCount]; boolean[] checkedChoices = new boolean[choicesCount]; for(int i=0; i<accounts.size(); i++) { Account account = accounts.elementAt(i); String label = getAccountLabel(account.type); choices[i] = account.name; if(!StringUtil.isNullOrEmpty(label)) { choices[i] += " (" + label + ")"; } checkedChoices[i] = true; } // Show Phone only contacts option if needed if(showPhoneOnlyOption) { choices[phoneOnlyOptionIndex] = localization .getLanguage("dialog_import_phone_contacts"); checkedChoices[phoneOnlyOptionIndex] = true; } // Show SIM contacts option if needed if(showSimOption) { choices[simOptionIndex] = localization .getLanguage("dialog_import_sim_contacts"); checkedChoices[simOptionIndex] = true; } MultipleChoicesClickListener listener = new MultipleChoicesClickListener(checkedChoices, accounts, phoneOnlyOptionIndex, simOptionIndex, isFirstImport); displayManager.promptMultipleSelection(getScreen(), title, okButtonLabel, cancelButtonLabel, choices, checkedChoices, listener, listener, listener); } }); } public static boolean contactsImported(Context c) { return ExternalAccountManager.getInstance(c).accountsImported(); } /** * @return the screen related to the screenId attribute */ private Screen getScreen() { if(screenId == Controller.HOME_SCREEN_ID) { return controller.getHomeScreenController().getHomeScreen(); } else if(screenId == Controller.ADVANCED_SETTINGS_SCREEN_ID) { return controller.getAdvancedSettingsScreenController().getAdvancedSettingsScreen(); } else { throw new IllegalStateException("Invalid screen id: " + screenId); } } private class ImportContactsTask extends AsyncTask<Void, Void, Integer> { private Vector<Account> accounts; private boolean includePhoneOnly; private boolean includeSim; private int dialogId = -1; private int total = 0; private int count = 0; private int syncLockId = SyncLock.FORBIDDEN; public ImportContactsTask(Vector<Account> accounts, boolean includePhoneOnly, boolean includeSim) { this.accounts = accounts; this.includePhoneOnly = includePhoneOnly; this.includeSim = includeSim; } /** * Shows the progress dialog */ @Override protected void onPreExecute() { // Before starting the actual import, we must make sure the sync // lock is not acquired, because we cannot perform an import if a // sync is in progress AppInitializer appInitializer = App.i().getAppInitializer(); SyncLock syncLock = appInitializer.getSyncLock(); syncLockId = syncLock.acquireLock(); if (syncLockId != SyncLock.FORBIDDEN) { String text = localization.getLanguage("dialog_importing_contacts"); dialogId = displayManager.showProgressDialog(getScreen(), text, false); } else { if (Log.isLoggable(Log.INFO)) { Log.info(TAG_LOG, "Cannot acquire sync lock, the import cannot be performed"); } // Show a timed alert and skip the import completely String text = localization.getLanguage("dialog_import_sync_in_progress"); displayManager.showMessage(getScreen(), text); } } /** * Imports the contacts and update the progress dialog * * @param params * @return the number of imported contacts, -1 in case of errors */ protected Integer doInBackground(Void... params) { int importedCount = 0; if (syncLockId != SyncLock.FORBIDDEN) { try { ExternalAccountManager m = ExternalAccountManager.getInstance(context); importedCount = m.importAccountItems(accounts, includePhoneOnly, includeSim, new ExternalAccountManager.ItemsImportListener() { public void setTotalItemsCount(int count) { ImportContactsTask.this.total = count; publishProgress(); } public void updateImportedItemsCount(int count) { ImportContactsTask.this.count = count; publishProgress(); } }); } catch(Exception ex) { Log.error(TAG_LOG, "Exception while importing contacts", ex); return -1; } } return importedCount; } /** * Update the progress dialog values * * @param values */ @Override protected void onProgressUpdate(Void... values) { displayManager.setProgressDialogMaxValue(dialogId, total); displayManager.setProgressDialogProgressValue(dialogId, count); } /** * Dismiss the progress dialog and shows import results * @param result */ @Override protected void onPostExecute(Integer result) { // First thing to do is to release the sync lock, so we are sure to // do it if (syncLockId != SyncLock.FORBIDDEN) { AppInitializer appInitializer = App.i().getAppInitializer(); SyncLock syncLock = appInitializer.getSyncLock(); syncLock.releaseLock(syncLockId); displayManager.dismissProgressDialog(getScreen(), dialogId); String text = null; if(result == 0) { text = localization.getLanguage("dialog_import_no_items"); } else if(result == -1) { text = localization.getLanguage("dialog_import_error"); } if(text != null) { OkCallBack okCallBack = new OkCallBack(); displayManager.showOkDialog(getScreen(), text, localization.getLanguage("dialog_ok"), okCallBack); } // Contacts have been imported. Now we have to make funambol the // default address book AppSyncSource appSyncSource = controller.getAppSyncSourceManager() .getSource(AppSyncSourceManager.CONTACTS_ID); ContactAppSyncSourceConfig config = (ContactAppSyncSourceConfig)appSyncSource.getConfig(); config.setMakeDefaultAddressBook(true); config.save(); } } } /** * Manages interactions with the multiple selection dialog */ private class MultipleChoicesClickListener implements DialogInterface.OnMultiChoiceClickListener, DialogInterface.OnClickListener { private boolean[] choices; private Vector<Account> accounts; private int phoneOnlyOptionIndex; private int simOptionIndex; private boolean isFirstImport; public MultipleChoicesClickListener(boolean[] choices, Vector<Account> accounts, int phoneOnlyOptionIndex, int simOptionIndex, boolean isFirstImport) { this.choices = choices; this.accounts = accounts; this.phoneOnlyOptionIndex = phoneOnlyOptionIndex; this.simOptionIndex = simOptionIndex; this.isFirstImport = isFirstImport; } // Called when a choice is clicked public void onClick(DialogInterface dialog, int whichButton, boolean isChecked) { choices[whichButton] = isChecked; } // Called when a button is clicked public void onClick(DialogInterface dialog, int whichButton) { if(whichButton == AlertDialog.BUTTON_POSITIVE) { Vector<Account> accountsToImport = new Vector<Account>(); for(int i=0; i<accounts.size(); i++) { if(choices[i]) { accountsToImport.add(accounts.elementAt(i)); } } boolean importPhoneContacts = phoneOnlyOptionIndex != -1 && choices[phoneOnlyOptionIndex]; boolean importSimItems = simOptionIndex != -1 && choices[simOptionIndex]; // Begin the actual import new ImportContactsTask(accountsToImport, importPhoneContacts, importSimItems).execute(); } else if(whichButton == AlertDialog.BUTTON_NEGATIVE && isFirstImport) { displayManager.showOkDialog(getScreen(), localization.getLanguage("dialog_import_later"), localization.getLanguage("dialog_ok")); } } } private String getAccountLabel(String accountType) { AuthenticatorDescription[] desc = AccountManager.get(context) .getAuthenticatorTypes(); PackageManager pm = context.getPackageManager(); for(AuthenticatorDescription des : desc) { if(accountType.equals(des.type)) { try { return pm.getText(des.packageName, des.labelId, null).toString(); } catch(Exception ex) { } } } Log.error(TAG_LOG, "Account label not found for type: " + accountType); return null; } private class OkCallBack implements Runnable { public void run() { if (Log.isLoggable(Log.TRACE)) { Log.trace(TAG_LOG, "Import terminated"); } // Signal to the controller that the import is complete // Note that the controller can be null if the import is not started // from the settings screen if (advSettController != null) { advSettController.importContactsCompleted(); } } } }