/* * Funambol is a mobile platform developed by Funambol, Inc. * Copyright (C) 2009 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 com.funambol.client.controller; import java.util.Vector; import com.funambol.client.customization.Customization; import com.funambol.client.configuration.Configuration; import com.funambol.client.source.AppSyncSource; import com.funambol.client.source.AppSyncSourceManager; import com.funambol.client.localization.Localization; import com.funambol.client.ui.AccountScreen; import com.funambol.client.ui.DisplayManager; import com.funambol.util.Log; import com.funambol.util.StringUtil; import com.funambol.sync.SyncException; /** * This class is the controller (in the MVC model) for the AccountScreen. */ public class AccountScreenController extends SynchronizationController { private static final String TAG_LOG = "AccountScreenController"; protected AccountScreen screen = null; protected AppSyncSource configAppSource = null; protected boolean failed = false; protected boolean sourceStarted = false; protected Exception exp = null; protected String originalUrl = null; protected String originalUser = null; protected String originalPassword = null; public AccountScreenController(Controller controller, AccountScreen accountScreen) { super(controller, accountScreen, null); this.screen = accountScreen; // Save the original values so that we can revert changes at any time originalUrl = configuration.getSyncUrl() != null ? configuration.getSyncUrl() : ""; originalUser = configuration.getUsername() != null ? configuration.getUsername() : ""; originalPassword = configuration.getPassword() != null ? configuration.getPassword() : ""; } /** * TODO: Remove once the com.funambol.client.controller package integration is finished */ public AccountScreenController(Controller controller, Customization customization, Configuration configuration, Localization localization, AppSyncSourceManager appSyncSourceManager, AccountScreen accountScreen) { super(controller, customization, configuration, localization, appSyncSourceManager, accountScreen, null); this.screen = accountScreen; // Save the original values so that we can revert changes at any time originalUrl = configuration.getSyncUrl() != null ? configuration.getSyncUrl() : ""; originalUser = configuration.getUsername() != null ? configuration.getUsername() : ""; originalPassword = configuration.getPassword() != null ? configuration.getPassword() : ""; } public AccountScreen getAccountScreen() { return screen; } public void saveAndCheck() { if(screen != null) { String serverUri; if (customization.syncUriEditable()) { serverUri = screen.getSyncUrl(); } else { serverUri = customization.getServerUriDefault(); } saveAndCheck(serverUri, screen.getUsername(), screen.getPassword()); } else { Log.error(TAG_LOG, "Cannot save, account screen is null"); } } public void saveAndCheck(String serverUri, String username, String password) { if (Log.isLoggable(Log.TRACE)) { Log.trace(TAG_LOG, "saveAndCheck"); } // Check if a sync is currently running, and in this case warn the user // that the account cannot be saved if (isSyncInProgress()) { showSyncInProgressMessage(); return; } // Trim all values so that spaces serverUri = serverUri.trim(); username = username.trim(); password = password.trim(); // Load all the default settings and overwrite the parameters edited // here configuration.load(); // Preliminary check if ( StringUtil.isNullOrEmpty(username) || StringUtil.isNullOrEmpty(password) || StringUtil.isNullOrEmpty(serverUri)) { showMessage(localization.getLanguage("login_failed_empty_params")); return; } else if (!StringUtil.isValidProtocol(serverUri)) { showMessage(localization.getLanguage("status_invalid_url")); return; } if( !originalUser.equals(serverUri) || !originalUser.equals(username) || !originalPassword.equals(password) || configuration.getCredentialsCheckPending()) { // Okay, save the configuration configuration.setSyncUrl(serverUri); configuration.setUsername(username); configuration.setPassword(password); // Reset the server dev inf configuration.setServerDevInf(null); if (configuration.save() != Configuration.CONF_OK) { showMessage(localization.getLanguage("message_config_error") + ": " + localization.getLanguage("message_config_error_save")); return; } // If the credentials check is not needed we directly authenticate // the user if(!customization.getCheckCredentialsInLoginScreen()) { new Thread() { public void run() { userAuthenticated(); } }.start(); return; } // Now we must perform a sync of the configuration to authenticate and // verify the credentials configAppSource = appSyncSourceManager.getSource( AppSyncSourceManager.CONFIG_ID); if (configAppSource == null) { Log.error(TAG_LOG, "No suitable ConfigSyncSource, cannot verify credentials"); } else { // Disable the save command failed = false; sourceStarted = false; exp = null; screen.disableSave(); Vector sources = new Vector(); sources.addElement(configAppSource); // We want to perform this sync at log level debug because if // this is the first sync the user has no means to set a log // level and we may need to check what's going on in case of // errors try { configuration.setTempLogLevel(Log.TRACE); controller.reapplyMiscConfiguration(); synchronize(SynchronizationController.MANUAL, sources); } catch (Exception e) { Log.error(TAG_LOG, "Config sync failed ", e); failed = true; syncEnded(); } finally { // Restore the original log level configuration.restoreLogLevel(); controller.reapplyMiscConfiguration(); } } } else { // There was no need to authenticate if (Log.isLoggable(Log.DEBUG)) { Log.debug(TAG_LOG, "No need to authenticate"); } } } protected boolean isSyncInProgress() { HomeScreenController homeScreenController = controller.getHomeScreenController(); return homeScreenController.isSynchronizing(); } public void sourceFailed(AppSyncSource appSource, SyncException e) { super.sourceFailed(appSource, e); // If the source failed due to a controlled interruption, we consider it // as successfull if (e.getCode() == SyncException.CONTROLLED_INTERRUPTION) { exp = null; failed = false; } // In order to guarantee compatibility with servers without a // ConfigSyncSource, we must allow users to access the home screen even // if the server sends a 404 status for this sync else if (e.getCode() != SyncException.NOT_FOUND_URI_ERROR) { exp = e; failed = true; } else { if (Log.isLoggable(Log.INFO)) { Log.info(TAG_LOG, "Server does not have a config source, most likely not a Funambol server"); } // Apply the default server configuration as the server did not // provide its capabilities. This has the side effect of hiding the // picture source controller.reapplyServerCaps(null); } } public void sourceStarted(AppSyncSource appSource) { super.sourceStarted(appSource); sourceStarted = true; } public void syncEnded() { super.syncEnded(); screen.enableSave(); if (failed && sourceStarted) { if (Log.isLoggable(Log.INFO)) { Log.info(TAG_LOG, "Cannot access home screen"); } screen.checkFailed(); // Clear the configuration for no pending credentials check configuration.setCredentialsCheckPending(true); configuration.save(); // Now we show an error to the user, depending on the error we got String msg; if (exp instanceof SyncException) { msg = getMessageFromSyncException((SyncException)exp); if(msg == null) { return; } } else { msg = localization.getLanguage("status_generic_error"); } // We should never fall into this case, unless we miss some strings // in the language table if (msg == null) { msg = localization.getLanguage("status_generic_error"); } // Show an error to the user showMessage(msg); } else if (!sourceStarted) { // Sync the source did not start, an appropriate error was displayed // by the syncrhonizationController if (Log.isLoggable(Log.INFO)) { Log.info(TAG_LOG, "Cannot access home screen"); } // Clear the configuration for no pending credentials check configuration.setCredentialsCheckPending(true); configuration.save(); screen.checkFailed(); } else { // The user is authenticated, hide the login and open the main view // screen userAuthenticated(); originalUrl = screen.getSyncUrl(); originalUser = screen.getUsername(); originalPassword = screen.getPassword(); } } protected String getMessageFromSyncException(SyncException ex) { String msg; switch (ex.getCode()) { case SyncException.AUTH_ERROR: msg = localization.getLanguage("status_invalid_credentials"); break; case SyncException.FORBIDDEN_ERROR: msg = localization.getLanguage("status_forbidden_error"); break; case SyncException.DATA_NULL: case SyncException.CONN_NOT_FOUND: msg = localization.getLanguage("status_invalid_url"); break; case SyncException.READ_SERVER_RESPONSE_ERROR: case SyncException.WRITE_SERVER_REQUEST_ERROR: case SyncException.SERVER_CONNECTION_REQUEST_ERROR: msg = localization.getLanguage("status_network_error"); break; case SyncException.CONNECTION_BLOCKED_BY_USER: msg = localization.getLanguage("status_connection_blocked"); break; case SyncException.CANCELLED: // In this case we shall simply go back to the account // screen, so we just return from this method msg = null; default: msg = localization.getLanguage("status_generic_error"); break; } return msg; } public boolean hasChanges(String serverUri, String username, String password) { if (customization.syncUriEditable()) { // We are a bit flexible in the URL comparison as we allow users to // change for example from http to https and viceversa String url1 = StringUtil.removeProtocolFromUrl(originalUrl); String url2 = StringUtil.removeProtocolFromUrl(serverUri); boolean sameUrl = url1.equals(url2); if (!sameUrl) { return true; } } if (!originalUser.equals(username) || !originalPassword.equals(password)) { return true; } return false; } public void resetValues() { if (customization.syncUriEditable()) { screen.setSyncUrl(originalUrl); configuration.setSyncUrl(originalUrl); } screen.setUsername(originalUser); screen.setPassword(originalPassword); configuration.setUsername(originalUser); configuration.setPassword(originalPassword); configuration.save(); } public void endSync(Vector sources, boolean hadErrors) { // Errors are handled in the syncEnded method setCancel(false); } public void hide() { controller.toBackground(); } public void initScreen() { Configuration config = controller.getConfiguration(); String url, usr, pwd; if (config.load() == Configuration.CONF_OK) { url = config.getSyncUrl(); usr = config.getUsername(); pwd = config.getPassword(); } else { Log.error(TAG_LOG, "Error loading the configuration, using default values"); url = customization.getServerUriDefault(); usr = customization.getUserDefault(); pwd = customization.getPasswordDefault(); } initScreen(url, usr, pwd); } public void initScreen(String url, String usr, String pwd) { if(screen != null) { screen.setSyncUrl(url); originalUrl = url; screen.setUsername(usr); originalUser = usr; screen.setPassword(pwd); originalPassword = pwd; } } protected void showSyncInProgressMessage() { // If the home screen is not displayed, we cannot show any warning and // just ignore this event if (screen != null) { DisplayManager dm = controller.getDisplayManager(); String msg = localization.getLanguage("message_sync_running_wait"); dm.showMessage(screen, msg); } } protected void userAuthenticated() { if (Log.isLoggable(Log.INFO)) { Log.info(TAG_LOG, "Opening home screen"); } // An account has been created. So keep track of it in order to not // display the signup screen again configuration.setSignupAccountCreated(true); configuration.setCredentialsCheckPending(false); configuration.save(); screen.checkSucceeded(); } public void switchToSignupScreen() { try { controller.getDisplayManager().showScreen(screen, Controller.SIGNUP_SCREEN_ID); controller.getDisplayManager().hideScreen(screen); } catch(Exception ex) { Log.error(TAG_LOG, "Unable to switch to login screen", ex); } } }