/*
* Funambol is a mobile platform developed by Funambol, Inc.
* Copyright (C) 2008 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 java.util.Enumeration;
import com.funambol.client.configuration.Configuration;
import com.funambol.client.localization.Localization;
import com.funambol.client.customization.Customization;
import com.funambol.client.controller.Controller;
import com.funambol.client.engine.SyncEngine;
import com.funambol.client.engine.Poller;
import com.funambol.client.engine.SyncEngineListener;
import com.funambol.client.engine.AppSyncRequest;
import com.funambol.client.source.AppSyncSource;
import com.funambol.client.source.AppSyncSourceManager;
import com.funambol.client.source.AppSyncSourceConfig;
import com.funambol.client.push.SyncScheduler;
import com.funambol.client.ui.Screen;
import com.funambol.client.ui.DisplayManager;
import com.funambol.platform.NetworkStatus;
import com.funambol.sync.SyncListener;
import com.funambol.sync.SyncSource;
import com.funambol.sync.SourceConfig;
import com.funambol.sync.SyncException;
import com.funambol.sapisync.sapi.SapiHandler;
import com.funambol.org.json.me.JSONObject;
import com.funambol.org.json.me.JSONArray;
import com.funambol.util.StringUtil;
import com.funambol.util.Log;
/**
* This interface includes all basic functions of a SynchronizationController
* implementation that are currently shared between Android and BlackBerry
* versions of SynchronizationController.
*/
public class SynchronizationController implements SyncEngineListener {
private static final String TAG_LOG = "SynchronizationController";
public static final int REFRESH_FROM_SERVER = 0;
public static final int REFRESH_TO_SERVER = 1;
public static final String MANUAL = "manual";
public static final String SCHEDULED = "scheduled";
public static final String PUSH = "push";
protected Configuration configuration;
protected Screen screen;
protected AppSyncSourceManager appSyncSourceManager;
protected Controller controller;
protected Customization customization;
protected Localization localization;
protected SyncEngine engine;
protected RequestHandler reqHandler;
protected final AppSyncRequest appSyncRequestArr[] = new AppSyncRequest[1];
protected SyncScheduler syncScheduler;
protected int RETRY_POLL_TIME = 1;
protected NetworkStatus networkStatus;
private Vector localStorageFullSources = new Vector();
private Vector serverQuotaFullSources = new Vector();
private SyncRequest currentRequest;
protected boolean doCancel = false;
protected AppSyncSource currentSource = null;
protected boolean showTCPAlert;
protected boolean logConnectivityError;
private int scheduledAttempt = 0;
private Poller retryPoller = null;
public SynchronizationController(Controller controller, Screen screen, NetworkStatus networkStatus) {
this.controller = controller;
this.screen = screen;
this.networkStatus = networkStatus;
configuration = controller.getConfiguration();
appSyncSourceManager = controller.getAppSyncSourceManager();
localization = controller.getLocalization();
customization = controller.getCustomization();
initSyncScheduler();
}
public SynchronizationController(Controller controller, Customization customization,
Configuration configuration, Localization localization,
AppSyncSourceManager appSyncSourceManager, Screen screen,
NetworkStatus networkStatus) {
this.controller = controller;
this.customization = customization;
this.configuration = configuration;
this.localization = localization;
this.appSyncSourceManager = appSyncSourceManager;
this.screen = screen;
this.networkStatus = networkStatus;
initSyncScheduler();
}
// TEMP TODO FIXME: This is here until the HomeScreenController is merged
// between android and BB
SynchronizationController() {
}
/**
* Triggers a synchronization for the given syncSources. The caller can
* specify its type (manual, scheduled, push) to change the error handling
* behavior
*
* @param syncType the caller type (SYNC_TYPE_MANUAL, SYNC_TYPE_SCHEDULED)
* @param syncSources is a vector of AppSyncSource to be synchronized
*
*/
public synchronized void synchronize(String syncType, Vector syncSources) {
synchronize(syncType, syncSources, 0);
}
/**
* Schedules a synchronization for the given syncSources. The sync is
* scheduled in "delay" milliseconds from now. The caller can
* specify its type (manual, scheduled, push) to change the error handling
* behavior
*
* @param syncType the caller type (SYNC_TYPE_MANUAL, SYNC_TYPE_SCHEDULED)
* @param syncSources is a vector of AppSyncSource to be synced
* @param delay the interval at which the sync shall be performed (relative
* to now)
*
*/
public synchronized void synchronize(String syncType, Vector syncSources, int delay) {
synchronize(syncType, syncSources, delay, false);
}
/**
* Perform a refresh for a set of sources and a given direction. The method
* gets blocked until the sync terminates.
*
* @param mask the set of sources to sync
* @param direction the refresh direction
*/
public void refresh(int mask, int direction) {
if (isSynchronizing()) {
return;
}
Enumeration sources = appSyncSourceManager.getEnabledAndWorkingSources();
Vector syncSources = new Vector();
while(sources.hasMoreElements()) {
AppSyncSource appSource = (AppSyncSource)sources.nextElement();
if ((appSource.getId() & mask) != 0) {
syncSources.addElement(appSource);
}
}
continueRefresh(syncSources, direction);
}
public synchronized void continueRefresh(Vector syncSources, int direction) {
// A refresh is always a manual sync, force the sync type here
currentRequest = new SyncRequest(MANUAL, syncSources, true, direction, 0, false);
forceSynchronization(currentRequest);
}
/**
* Schedules a synchronization for the given syncSources. The sync is
* scheduled in "delay" milliseconds from now. The caller can
* specify its type (manual, scheduled, push) to change the error handling
* behavior.
* The caller can also specify it the sync request is generated outside of
* the application. In such a case the handling is special and the
* synchronization is actually performed by the Sync Client process. This
* calls notifies the SyncClient to schedule a sync at the given interval.
* This is useful when syncs are triggered on external events, such as
* modification of PIM (c2s push).
*
* @param syncType the caller type (SYNC_TYPE_MANUAL, SYNC_TYPE_SCHEDULED)
* @param syncSources is a vector of AppSyncSource to be synced
* @param delay the interval at which the sync shall be performed (relative
* to now)
* @param fromOutside specifies if the request is generated outside of the
* application
*/
public synchronized void synchronize(String syncType, Vector syncSources,
int delay, boolean fromOutside) {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "synchronize " + syncType);
}
if (isSynchronizing()) {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "A sync is already in progress");
}
return;
}
currentRequest = new SyncRequest(syncType, syncSources, false, 0, delay, fromOutside);
forceSynchronization(currentRequest);
}
protected synchronized void forceSynchronization(SyncRequest syncRequest) {
// Search if at least one of the selected sources has a warning on the
// first sync
Vector syncSources = syncRequest.getSources();
String syncType = syncRequest.getType();
int delay = syncRequest.getDelay();
boolean fromOutside = syncRequest.getFromOutside();
boolean refresh = syncRequest.getRefresh();
int direction = syncRequest.getDirection();
Vector sourcesWithQuestion = new Vector();
syncSources = applyBandwidthSaver(syncSources, sourcesWithQuestion, syncType);
// We cannot ask the question if there is no app visible
if (screen == null && sourcesWithQuestion.size() > 0) {
AppSyncSource[] dialogDependentSources = new AppSyncSource[sourcesWithQuestion.size()];
sourcesWithQuestion.copyInto(dialogDependentSources);
} else {
if (sourcesWithQuestion.isEmpty()) {
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "Continue sync without prompts");
}
//No dialog is prompted for any sources: the sync can begin
continueSynchronizationAfterBandwithSaverDialog(syncRequest);
} else {
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "Continue sync displaying bandwith prompt");
}
ContinueAfterBandwidthSaverAction cabsa = new ContinueAfterBandwidthSaverAction(syncRequest);
InterruptSyncAction isa = new InterruptSyncAction();
DialogController dialControll = controller.getDialogController();
dialControll.askContinueCancelQuestion(screen,localization.getLanguage("dialog_no_wifi_availabale"), false,
cabsa, isa);
//The sync request is started when the user has finished to reply
//all the first sync request dialogs (the last sync request dialog)
//(calling the continueSynchronizationAfterDialogCheck method)
}
}
}
/**
* Displays warnings in the proper form if the outcome of the latest sync requires so.
* This method must be called when all synchronization operations are finished and the
* user can be warned about problems that trigger a notification or a pop-up message
* like those connected with storage limits (locally or in the cloud).
*/
protected void displayEndOfSyncWarnings() {
if (localStorageFullSources != null && localStorageFullSources.size() > 0) {
Log.debug(TAG_LOG, "Notifying storage limit warning");
displayStorageLimitWarning(localStorageFullSources);
localStorageFullSources.removeAllElements();
}
if (serverQuotaFullSources != null && serverQuotaFullSources.size() > 0) {
Log.debug(TAG_LOG, "Notifying server quota warning");
displayServerQuotaWarning(serverQuotaFullSources);
serverQuotaFullSources.removeAllElements();
}
}
protected void checkSourcesForStorageOrQuotaFullErrors(Vector sources) {
for (int i = 0; i < sources.size(); i++) {
AppSyncSource appSource = (AppSyncSource) sources.elementAt(i);
switch (appSource.getConfig().getLastSyncStatus()) {
case SyncListener.LOCAL_CLIENT_FULL_ERROR:
// If one of the sources has risked to break the storage limit,
// a warning message can have to be displayed
localStorageFullSources.addElement(appSource);
break;
case SyncListener.SERVER_FULL_ERROR:
serverQuotaFullSources.addElement(appSource);
break;
}
}
}
/**
* Display a background notification when max storage limit on local device
* is reached. Children can override this method and implement
* a foreground behavior
* @param localStorageFullSources
*/
protected void displayStorageLimitWarning(Vector localStorageFullSources) {
controller.getNotificationController().showNotificationClientFull();
localStorageFullSources.removeAllElements();
}
/**
* Display a background notification when server quota is reached. Children
* can override this method and implement a foreground behavior
* @param serverQuotaFullSources
*/
protected void displayServerQuotaWarning(Vector serverQuotaFullSources) {
controller.getNotificationController().showNotificationServerFull();
serverQuotaFullSources.removeAllElements();
}
protected SyncEngine createSyncEngine() {
return new SyncEngine(customization, configuration, appSyncSourceManager, null);
}
/**
* Applies the Bandwidth Saver by filtering out some sources or by populating
* the Vector of sources that need to be synchronized only if the user accepts
* to do so.
* The synchronizations for sources that are filtered out are immediately set
* as pending and terminated.
* This method has to be called before the synchronizations actually start.
*
* @param syncSources all sources to be synchronized
* @param sourcesWithQuestion an empty Vector
* @param syncType the synchronization type
* @return a sub-vector of sync sources containing only those sources that have
* passed the check
*/
protected Vector applyBandwidthSaver(Vector syncSources, Vector sourcesWithQuestion, String syncType) {
// This class cannot guarantee that these two members are not null,
// therefore we check for their validity and do not filter if any of
// these two is undefined
if (configuration == null || networkStatus == null) {
return syncSources;
}
if (configuration.getBandwidthSaverActivated() && !networkStatus.isWiFiConnected()) {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Bandwidth saver is enabled, wifi not connected and sync type " + syncType);
}
// If the syncType is automatic (i.e. not manual) and WiFi is not available,
// we shall skip all the sources which are to be synchronized only in WiFi
if (!MANUAL.equals(syncType)) {
Vector prefilteredSources = new Vector();
for (int i = 0; i < syncSources.size(); ++i) {
AppSyncSource appSource = (AppSyncSource)syncSources.elementAt(i);
// We need to check if the source requires to be sync'ed only in WiFi
// In v9 we excluded also sync sources with online quota full, but this
// behavior was modified in v10
if (appSource.getBandwidthSaverUse()) {
// Skip this source because of the Bandwidth Saver.
// Remember that we have a pending sync now
AppSyncSourceConfig sourceConfig = appSource.getConfig();
sourceConfig.setPendingSync(syncType, sourceConfig.getSyncMode());
configuration.save();
// The sync for this source is terminated
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Ignoring sync for source: " + appSource.getName());
}
sourceEnded(appSource);
} else {
// It's OK
prefilteredSources.addElement(appSource);
}
}
syncSources = prefilteredSources;
} else {
// Now check if any source to be synchronized requires user confirmation
// because of the bandwidth saver
for(int y = 0; y < syncSources.size(); ++y) {
AppSyncSource appSource = (AppSyncSource)syncSources.elementAt(y);
if(appSource.getBandwidthSaverUse()) {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Found a source which requires bandwidth saver question");
}
sourcesWithQuestion.addElement(appSource);
}
}
}
}
return syncSources;
}
protected void initSyncScheduler() {
engine = createSyncEngine();
syncScheduler = new SyncScheduler(engine);
// The request handler is a daemon serving external requests
reqHandler = new RequestHandler();
reqHandler.start();
}
/**
* Returns true iff a synchronization is in progress
*/
public boolean isSynchronizing() {
return engine.isSynchronizing();
}
/**
* Returns the sync source currently being synchronized. If a sync is not
* in progress, then null is returned. Please note that this method is not
* completely equivalent to isSynchronizing. At the beginning of a sync,
* isSynchronizing returns true, but getCurrentSource may return null until
* the source is prepared for the synchronization.
*/
public AppSyncSource getCurrentSource() {
return engine.getCurrentSource();
}
/**
* @return the current <code>SyncEngine</code> instance
*/
public SyncEngine getSyncEngine() {
return engine;
}
/**
* Try to cancel the current sync. This works for cooperative sources that
* check the synchronizationController status.
*/
public void cancelSync() {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Cancelling sync " + isSynchronizing() + " currentSource=" + currentSource);
}
setCancel(true);
if (isSynchronizing() && currentSource != null) {
UISyncSourceController uiSourceController = currentSource.getUISyncSourceController();
if (uiSourceController != null) {
uiSourceController.startCancelling();
}
engine.cancelSync();
}
}
public void syncEnded() {
displayEndOfSyncWarnings();
}
protected void showMessage(String msg) {
controller.getDisplayManager().showMessage(screen, msg);
}
public void noCredentials() {
showMessage(localization.getLanguage("message_login_required"));
}
public void noSources() {
showMessage(localization.getLanguage("message_nothing_to_sync"));
}
public void noConnection() {
showMessage(localization.getLanguage("message_radio_off"));
}
public void noSignal() {
showMessage(localization.getLanguage("message_no_signal"));
}
public void setCancel(boolean value) {
doCancel = value;
}
/**
* Check if the current sync should be cancelled
*
* @return
*/
public boolean isCancelled() {
return doCancel;
}
public void beginSync() {
clearErrors();
setCancel(false);
}
public boolean syncStarted(Vector sources) {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "syncStarted");
}
return true;
}
public void endSync(Vector sources, boolean hadErrors) {
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "endSync reached");
}
setCancel(false);
// Disable the retry poller if not null
if(retryPoller != null) {
retryPoller.disable();
retryPoller = null;
}
// If we had a CONNECTION BLOCKED BY THE USER error (user does not allow
// any network configuration) then we show an error because the user
// had already interacted with the app for this sync
if (hadErrors && showTCPAlert) {
controller.toForeground();
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "showing tcp settings alert!");
}
showMessage(localization.getLanguage("message_enter_TCP_settings"));
}
// Re-checks
checkSourcesForStorageOrQuotaFullErrors(sources);
// We reset these errors because this sync is over (if we are retrying,
// we must consider the new one with no errors)
logConnectivityError = false;
showTCPAlert = false;
}
public void sourceStarted(AppSyncSource appSource) {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "sourceStarted " + appSource.getName());
}
currentSource = appSource;
UISyncSourceController sourceController = appSource.getUISyncSourceController();
if (sourceController != null) {
sourceController.setSelected(true, false);
}
if (currentSource.getSyncSource().getConfig().getSyncMode() == SyncSource.FULL_DOWNLOAD) {
refreshClientData(appSource, sourceController);
}
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "sourceStarted " + currentSource);
}
}
public void sourceEnded(AppSyncSource appSource) {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "sourceEnded " + appSource.getName());
}
currentSource = null;
// Set synced source
appSource.getConfig().setSynced(true);
saveSourceConfig(appSource);
UISyncSourceController sourceController = appSource.getUISyncSourceController();
if (sourceController != null) {
sourceController.setSelected(false, false);
}
}
public void sourceFailed(AppSyncSource appSource, SyncException e) {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "sourceFailed");
}
int code = e.getCode();
if ( code == SyncException.READ_SERVER_RESPONSE_ERROR
|| code == SyncException.WRITE_SERVER_REQUEST_ERROR
|| code == SyncException.CONN_NOT_FOUND) {
logConnectivityError = true;
} else if (code == SyncException.CONNECTION_BLOCKED_BY_USER) {
showTCPAlert = true;
} else if (code == SyncException.PAYMENT_REQUIRED) {
// In order to sync the user shall accept a payment
// Compute the remaining sources
Vector nextSources = new Vector();
Vector originalSources = currentRequest.getSources();
boolean include = false;
for(int i=0;i<originalSources.size();++i) {
AppSyncSource ss = (AppSyncSource)originalSources.elementAt(i);
if (ss.getId() == appSource.getId()) {
include = true;
}
if (include) {
nextSources.addElement(ss);
}
}
// We ask for payment only if the server is a Funambol cared, as
// this is not a standard SyncML mechanism
if (configuration.getServerType() == Configuration.SERVER_TYPE_FUNAMBOL_CARED) {
askForPayment(nextSources);
}
}
}
public String getRemoteUri(AppSyncSource appSource) {
SourceConfig config = appSource.getSyncSource().getConfig();
return config.getRemoteUri();
}
public void serverOperationFailed() {
showMessage(localization.getLanguage("message_not_send_to_server"));
}
public Controller getController() {
return controller;
}
public void clearErrors() {
showTCPAlert = false;
logConnectivityError = false;
}
protected void setScreen(Screen screen) {
this.screen = screen;
}
private void saveSourceConfig(AppSyncSource appSource) {
appSource.getConfig().saveSourceSyncConfig();
appSource.getConfig().commit();
}
private boolean retry(Vector sources) {
boolean willRetry = false;
if (retryPoller != null) {
retryPoller.disable();
}
if (scheduledAttempt < 3) {
scheduledAttempt++;
Log.error(TAG_LOG, "Scheduled sync: Connection attempt failed. " + "Try again in "
+ RETRY_POLL_TIME + " minutes");
retryPoller = new Poller(this, RETRY_POLL_TIME, true, false);
retryPoller.start();
willRetry = true;
} else {
retryPoller = null;
scheduledAttempt = 0;
}
return willRetry;
}
protected void askForPayment(Vector nextSources) {
// On BB dialogs are blocking, here we really need to create a thread so
// that the current sync terminates and a new one is restarted afterward
// (otherwise events get messed up)
// Creating the thread on all platforms is a safe solution.
PaymentThread pt = new PaymentThread(nextSources, currentRequest);
pt.start();
}
protected class PaymentYesAction implements Runnable {
private Vector remainingSources;
private SyncRequest originalRequest;
public PaymentYesAction(Vector remainingSources, SyncRequest originalRequest) {
this.remainingSources = remainingSources;
this.originalRequest = originalRequest;
}
public void run() {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "User accepted payment request, continue sync");
}
// Show a progress dialog while the payment is being performed
DisplayManager dm = controller.getDisplayManager();
int progressDialogId = dm.showProgressDialog(screen, "Payment in progress");
SapiPaymentThread spt = new SapiPaymentThread(progressDialogId, remainingSources, originalRequest);
spt.start();
}
}
protected class SapiPaymentThread extends Thread {
private int progressDialogId;
private SyncRequest originalRequest;
private Vector remainingSources;
public SapiPaymentThread(int progressDialogId, Vector remainingSources, SyncRequest originalRequest) {
this.progressDialogId = progressDialogId;
this.remainingSources = remainingSources;
this.originalRequest = originalRequest;
}
public void run() {
try {
String sapiUrl = StringUtil.extractAddressFromUrl(configuration.getSyncUrl());
SapiHandler sapiHandler = new SapiHandler(sapiUrl, configuration.getUsername(),
configuration.getPassword());
JSONObject req = new JSONObject();
JSONArray sources = new JSONArray();
Enumeration workingSources = appSyncSourceManager.getWorkingSources();
while(workingSources.hasMoreElements()) {
AppSyncSource appSource = (AppSyncSource)workingSources.nextElement();
JSONObject restoreSource = new JSONObject();
restoreSource.put("service","restore");
restoreSource.put("resource",appSource.getSyncSource().getConfig().getRemoteUri());
sources.put(restoreSource);
}
req.put("data", sources);
sapiHandler.query("system/payment","buy",null,null,req);
// Restart the sync for the given sources
continueSyncAfterNetworkUsage(originalRequest.getType(), remainingSources,
originalRequest.getRefresh(), originalRequest.getDirection(),
originalRequest.getDelay(), originalRequest.getFromOutside());
} catch (Exception e) {
Log.error(TAG_LOG, "Cannot perform payment", e);
// TODO FIXME: show an error to the user
} finally {
// Dismiss the progress dialog
DisplayManager dm = controller.getDisplayManager();
dm.dismissProgressDialog(screen, progressDialogId);
}
}
}
protected class PaymentNoAction implements Runnable {
public void run() {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "User did not accept payment request, stop sync");
}
}
}
protected class PaymentThread extends Thread {
private Vector sources;
private SyncRequest syncRequest;
public PaymentThread(Vector sources, SyncRequest syncRequest) {
this.sources = sources;
this.syncRequest = syncRequest;
}
public void run() {
DialogController dc = controller.getDialogController();
String syncType = com.funambol.client.controller.SynchronizationController.MANUAL;
PaymentYesAction yesAction = new PaymentYesAction(sources, syncRequest);
PaymentNoAction noAction = new PaymentNoAction();
dc.askYesNoQuestion(screen, localization.getLanguage("dialog_payment_required"), false, yesAction, noAction);
}
}
protected void continueSyncAfterNetworkUsage(String syncType, Vector syncSources, boolean refresh,
int direction, int delay, boolean fromOutside)
{
// We register as listeners for the sync
engine.setListener(this);
int sourceSyncType = 0;
AppSyncRequest appSyncRequest = new AppSyncRequest(null, delay);
Enumeration sources = syncSources.elements();
while(sources.hasMoreElements()) {
AppSyncSource appSource = (AppSyncSource) sources.nextElement();
SyncSource source = appSource.getSyncSource();
if (refresh) {
int syncMode = appSource.prepareRefresh(direction);
source.getConfig().setSyncMode(syncMode);
} else {
sourceSyncType = appSource.getConfig().getSyncMode();
// If this source has no config set, then we cannot force a sync
// mode, but this is a logical error
if (source.getConfig() != null) {
source.getConfig().setSyncMode(sourceSyncType);
} else {
Log.error(TAG_LOG, "Source has no config, cannot set sync mode");
}
}
// Clear any pending sync information here, because we are about to
// start a sync
AppSyncSourceConfig sourceConfig = appSource.getConfig();
sourceConfig.setPendingSync("", -1);
configuration.save();
// Add the request for this synchronization
appSyncRequest.addRequestContent(appSource);
}
if (fromOutside) {
synchronized(appSyncRequestArr) {
appSyncRequestArr[0] = appSyncRequest;
appSyncRequestArr.notify();
}
} else {
syncScheduler.addRequest(appSyncRequest);
}
}
protected synchronized void
continueSynchronizationAfterBandwithSaverDialog(SyncRequest syncRequest) {
// If no sources left, we simply return and do not update/change
// anything
if (syncRequest.getSources().isEmpty()) {
syncEnded();
return;
}
// Ask network usage permission if required
ContinueSyncAction csa = new ContinueSyncAction(syncRequest);
NetworkUsageWarningController nuwc = new NetworkUsageWarningController(screen, controller, csa);
nuwc.askUserNetworkUsageConfirmation();
}
protected String getListOfSourceNames(Enumeration sourceNameList) {
StringBuffer sourceNames = new StringBuffer();
int x = 0;
AppSyncSource appSource = (AppSyncSource)sourceNameList.nextElement();
while(appSource != null) {
String name = appSource.getName();
appSource = (AppSyncSource)sourceNameList.nextElement();
if (x > 0) {
sourceNames.append(", ");
if (appSource == null) {
sourceNames.append(localization.getLanguage("dialog_and").toLowerCase());
}
}
sourceNames.append(name);
}
return sourceNames.toString();
}
private class RequestHandler extends Thread {
private boolean stop = false;
public RequestHandler() {
}
public void run() {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Starting request handler");
}
while (!stop) {
try {
synchronized (appSyncRequestArr) {
appSyncRequestArr.wait();
syncScheduler.addRequest(appSyncRequestArr[0]);
}
} catch (Exception e) {
// All handled exceptions are trapped below, this is just a
// safety net for runtime exception because we don't want
// this thread to die.
Log.error(TAG_LOG, "Exception while performing a programmed sync " + e.toString());
}
}
}
}
protected class ContinueRefreshAction implements Runnable {
private Vector sources;
private int direction;
public ContinueRefreshAction(Vector sources, int direction) {
this.sources = sources;
this.direction = direction;
}
public void run() {
continueRefresh(sources, direction);
}
}
protected class ContinueSyncAction implements Runnable {
private SyncRequest request;
public ContinueSyncAction(SyncRequest syncRequest) {
this.request = syncRequest;
}
public void run() {
continueSyncAfterNetworkUsage(request.getType(), request.getSources(),
request.getRefresh(), request.getDirection(),
request.getDelay(), request.getFromOutside());
}
}
protected class InterruptSyncAction implements Runnable {
public void run() {
syncEnded();
}
}
protected class ContinueAfterBandwidthSaverAction implements Runnable {
private SyncRequest request;
public ContinueAfterBandwidthSaverAction(SyncRequest syncRequest) {
this.request = syncRequest;
}
public void run() {
continueSynchronizationAfterBandwithSaverDialog(request);
}
}
protected class SyncRequest {
private String syncType;
private Vector sources;
private boolean refresh;
private int direction;
private int delay;
private boolean fromOutside;
public SyncRequest(String syncType, Vector syncSources, boolean refresh, int direction,
int delay, boolean fromOutside)
{
this.syncType = syncType;
this.sources = syncSources;
this.refresh = refresh;
this.direction = direction;
this.delay = delay;
this.fromOutside = fromOutside;
}
public String getType() {
return syncType;
}
public Vector getSources() {
return sources;
}
public boolean getRefresh() {
return refresh;
}
public int getDirection() {
return direction;
}
public int getDelay() {
return delay;
}
public boolean getFromOutside() {
return fromOutside;
}
}
private void refreshClientData(AppSyncSource appSource, UISyncSourceController controller) {
}
}