/*
* 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.Enumeration;
import java.util.Vector;
import com.funambol.client.localization.Localization;
import com.funambol.client.ui.DisplayManager;
import com.funambol.client.ui.Screen;
import com.funambol.client.source.AppSyncSource;
import com.funambol.client.source.AppSyncSourceManager;
import com.funambol.util.Log;
/**
* Control dialog alert flow on the client using the client's controller and
* DisplayManager. This class is just a controller. Refer to DisplayManager
* implementation in order to manage the alert diplaying logic.
*/
public class DialogController {
/** TAG to be displayed into log messages*/
public static final String TAG_LOG = "DialogController";
//--- Local instance fields fed by the constructor
private Localization localization;
private AppSyncSourceManager appSyncSourceManager;
private Controller controller;
private DisplayManager displayManager;
//--- Current Alert Status related fields
//Last selected direction to be used in case of resume of the reset type
//alert dialog. The
private int lastSelectedDirection;
//Last first sync alert status use this field in order to resume itself
//The field is built in the constructor and is updated whenever a first
//sync dialog alert is prompted
FirstSyncDialogState lastFirstSyncDialogState = new FirstSyncDialogState();
//Last WI-Fi not available alert status use this field in order to resume itself
//The field is built in the constructor and is updated whenever a first
//sync dialog alert is prompted
WifiNotAvailableDialogState lastWifiNotAvailableDialogState = new WifiNotAvailableDialogState();
/**
* Public constructor
* @param displayManager the DisplayManager object to be used.
* @param controller
*/
public DialogController(DisplayManager displayManager, Controller controller) {
this.displayManager = displayManager;
this.controller = controller;
this.localization = controller.getLocalization();
this.appSyncSourceManager = controller.getAppSyncSourceManager();
this.lastFirstSyncDialogState = new FirstSyncDialogState();
this.lastWifiNotAvailableDialogState = new WifiNotAvailableDialogState();
this.lastSelectedDirection = -1;
}
/**
* Prompt a message alert on the screen
* @param message is the String to be displayed on the screen
* @return boolean true if the alert was displayed.
*/
public boolean promptNext(String message) {
return displayManager.promptNext(message);
}
/**
* Not implemented.
* @param message the message to be displayed
* @param defaultyes the default parameter in the selection
* @return boolean true is the user answered yes, false otherwise
*/
public boolean askYesNoQuestion(String message, boolean defaultyes) {
// TODO FIXME: to be implemented
return false;
}
/**
* Prompt an alert with 2 choices on the screen
* @param message the message to be displayed
* @param defaultyes the default parameter in the selection
* @return boolean true is the user accepted, false otherwise
*/
public boolean askAcceptDenyQuestion(String message, boolean defaultyes) {
return displayManager.askAcceptDenyQuestion(message, defaultyes, -1);
}
/**
* Not implemented.
* @param message the message to be displayed
* @param defaultyes the default parameter in the selection
* @param timeToWait the time to wait before dismiss the alert
* @return boolean true is the user answered yes, false otherwise
*/
public boolean askYesNoQuestion(String message, boolean defaultyes, int timeToWait) {
// TODO FIXME: to be implemented
return false;
}
/**
* Show an alert message on the screen
* @param screen the dialog alert owner Screen
* @param language the message to be dispalyed
*/
public void showMessage(Screen screen, String language) {
displayManager.showMessage(screen, language);
}
/**
* Show an alert message on the screen for a given amount of time
* @param screen the dialog alert owner Screen
* @param language the message to be dispalyed
* @param delay the duration of the message in milliseconds
*/
public void showMessage(Screen screen, String language, int delay){
displayManager.showMessage(screen, language, delay);
}
/**
* Use the localization field to build the reset direction alert dialog.
* This dialog builds ResetDirectionDialogOption objects to refer to the
* dialog choiches.
* @param screen the dialog alert owner Screen
*/
public void showRefreshDirectionDialog(Screen screen) {
DialogOption[] opt = new DialogOption[3];
opt[0] = new ResetDirectionDialogOption(
screen,
localization.getLanguage("dialog_refresh_from"),
SynchronizationController.REFRESH_FROM_SERVER);
opt[1] = new ResetDirectionDialogOption(
screen,
localization.getLanguage("dialog_refresh_to"),
SynchronizationController.REFRESH_TO_SERVER);
opt[2] = new ResetDirectionDialogOption(
screen,
localization.getLanguage("dialog_cancel"),
-1);
displayManager.promptSelection(screen, localization.getLanguage("dialog_refresh_which") + "\n"
+ localization.getLanguage("dialog_refresh_warn2"), opt, -1,
displayManager.REFRESH_DIRECTION_DIALOG_ID);
}
/**
* Use the localization field to build the wifi not available alert dialog.
* This dialog builds WIFINotAvailableDialogOption objects to refer to the
* dialog choiches.
* @param screen the dialog alert owner Screen
*/
public void showNoWIFIAvailableDialog(Screen screen,
String syncType,
Vector filteredSources,
boolean refresh,
int direction,
int delay,
boolean fromOutside)
{
DialogOption[] opt = new DialogOption[2];
opt[0] = new WIFINotAvailableDialogOption(
screen,
localization.getLanguage("dialog_continue"),
0,
syncType,
filteredSources,
refresh,
direction,
delay,
fromOutside);
opt[1] = new WIFINotAvailableDialogOption(
screen,
localization.getLanguage("dialog_cancel"),
-1,
syncType,
filteredSources,
refresh,
direction,
delay,
fromOutside);
lastWifiNotAvailableDialogState.update(syncType, filteredSources,
refresh, direction, delay, fromOutside);
displayManager.promptSelection(screen, localization.getLanguage("dialog_no_wifi_availabale"),
opt, 0, displayManager.NO_WIFI_AVAILABLE_ID);
}
/**
* Dialog option related to the wifi not available.
*/
protected class WIFINotAvailableDialogOption extends DialogOption {
private String syncType;
private Vector filteredSources;
private boolean refresh;
private int direction;
private int delay;
private boolean fromOutside;
public WIFINotAvailableDialogOption(Screen screen, String description, int value,
String syncType,
Vector filteredSources,
boolean refresh,
int direction,
int delay,
boolean fromOutside) {
super(screen, description, value);
this.filteredSources = filteredSources;
this.refresh = refresh;
this.direction = direction;
this.delay = delay;
this.fromOutside = fromOutside;
this.syncType = syncType;
// The default action is to cancel
if (value == -1) {
displayManager.addPostDismissSelectionDialogAction(displayManager.NO_WIFI_AVAILABLE_ID, this);
}
}
/**
* Triggered in threaded like logic when the user selects an option.
*/
public void run() {
displayManager.removePostDismissSelectionDialogAction(displayManager.NO_WIFI_AVAILABLE_ID);
displayManager.dismissSelectionDialog(displayManager.NO_WIFI_AVAILABLE_ID);
HomeScreenController hsCont = controller.getHomeScreenController();
if (getValue() == 0) {
if(!hsCont.isSynchronizing()) {
hsCont.changeSyncLabelsOnSyncEnded();
}
hsCont.continueSynchronizationAfterBandwithSaverDialog(syncType, filteredSources,
refresh, direction,
delay, fromOutside, true);
} else {
hsCont.continueSynchronizationAfterBandwithSaverDialog(syncType, new Vector(),
refresh, direction,
delay, fromOutside, false);
}
}
}
/**
* Use the localization field to build the reset type alert dialog that use
* the DisplayManager client implementation to ask the user whose sources
* must be refreshed. This dialog builds ResetTypeDialogOption objects to
* refer to the dialog choiches. Actual client implementation requires this
* alert to be diaplayed after the showRefreshDirectionDialog call in order
* to have full information abaout the direction.
* @param screen the dialog alert owner Screen
* @param int the refresh direction for the selected sources.
*/
public void showRefreshTypeDialog(Screen screen, int direction) {
ResetTypeDialogOption[] options = null;
// Count the number of enabled and refreshable sources
Vector opts = new Vector();
int numEnabledSources = -1;
Enumeration enabledSources = appSyncSourceManager.getEnabledAndWorkingSources();
int allId = 0;
while(enabledSources.hasMoreElements()) {
AppSyncSource source = (AppSyncSource)enabledSources.nextElement();
if (source.isRefreshSupported(direction) && source.isVisible() &&
source.getConfig().getActive() && source.isWorking()) {
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "Source: " + source.getName() + " direction " + direction +
" supported " + source.isRefreshSupported(direction));
}
opts.addElement(new ResetTypeDialogOption(screen, source.getName(), source.getId(), direction));
numEnabledSources++;
allId |= source.getId();
}
}
if ((numEnabledSources + 1) > 1) {
opts.addElement(new ResetTypeDialogOption(
screen,
localization.getLanguage("type_all_enabled"),
allId,
direction));
}
opts.addElement(new ResetTypeDialogOption(
screen,
localization.getLanguage("dialog_cancel"),
0,
direction));
options = new ResetTypeDialogOption[opts.size()];
opts.copyInto(options);
displayManager.promptSelection(screen, localization.getLanguage("dialog_refresh")
+ " "
+ (direction == SynchronizationController.REFRESH_FROM_SERVER ? localization
.getLanguage("dialog_refresh_from").toLowerCase() : localization
.getLanguage("dialog_refresh_to")).toLowerCase(), options, -1,
displayManager.REFRESH_TYPE_DIALOG_ID);
}
/**
* Dialog option related to the refresh direction to be used.
*/
protected class ResetDirectionDialogOption extends DialogOption {
public ResetDirectionDialogOption(Screen screen, String description, int value) {
super(screen, description, value);
}
/**
* Triggered in threaded like logic when the user selects an option.
*/
public void run() {
//Dismiss the currect dialog
displayManager.dismissSelectionDialog(displayManager.REFRESH_DIRECTION_DIALOG_ID);
lastSelectedDirection = value;
//if the user selected a direction the refresh type dialog is shown
//with the message related to that sync direction
if (!this.getDescription().equals(localization.getLanguage("dialog_cancel"))) {
showRefreshTypeDialog(screen, value);
}
}
}
/**
* Resumes the last refresh type alert dialog if it was paused before the
* user answered. This could be due to any pausing event on the client side
* (screen rotation, incoming calls ...).
* This method works if and only if a previous call to the
* method was performed because it needs the last saved state of the field
* lastSelectedDirection that is updated by the RefreshDirectionDialog class
* when the user chose the reset direction
* @param screen the dialog alert owner Screen
*/
public void resumeLastRefreshTypeDialog(Screen screen) {
showRefreshTypeDialog(screen, lastSelectedDirection);
}
/**
* Contains the state of the last first sync alert dialog request.
*/
class FirstSyncDialogState {
protected AppSyncSource[] appSourceList;
protected String syncType;
protected Vector filteredSources;
protected int delay;
protected boolean refresh;
protected int direction;
protected boolean fromOutside;
protected int questionCounter;
protected int sourceIndex;
/**
* Update this class fields with the current FirstSyncDialogOption state
* @param appSourceList the list of appSyncSources
* @param syncType the String representation for the sync type
* (manual...)
* @param filteredSources the sources Vector to be updated in case the
* user select to sync now
* @param delay request the sync scheduler to be initiate the sync after
* the given amount of milliseconds
* @param fromOutside used by the sync scheduler to manage the incoming
* sync request from outside if true
* @param questionCounter the number of question to be displayed to the
* user. This number depends by the number of sources that have a
* warning messgae to be displayed at the first sync
* @param sourceIndex the sync source index source which this dialog
* alert is related.
*/
protected void update(AppSyncSource[] appSourceList, String syncType,
Vector filteredSources, boolean refresh,
int direction, int delay, boolean fromOutside,
int questionCounter, int sourceIndex) {
this.appSourceList = appSourceList;
this.syncType = syncType;
this.filteredSources = filteredSources;
this.refresh = refresh;
this.direction = direction;
this.delay = delay;
this.fromOutside = fromOutside;
this.questionCounter = questionCounter;
this.sourceIndex = sourceIndex;
}
}
/**
* Resumes the last fisrt sync alert dialog if it was paused before the
* user answered. This could be due to any pausing event on the client side
* (screen rotation, incoming calls ...).
* This method works if and only if a previous call to the showFirstSyncDialog
* method was performed because it needs the last saved state of the field
* lastFirstSyncDialogState that is updated by the showFirstSyncDialog method
* when called.
* @param screen the dialog alert owner Screen
*/
public void resumeLastFirstSyncDialog(Screen screen) {
showFirstSyncDialog(screen, lastFirstSyncDialogState.appSourceList,
lastFirstSyncDialogState.syncType,
lastFirstSyncDialogState.filteredSources,
lastFirstSyncDialogState.refresh,
lastFirstSyncDialogState.direction,
lastFirstSyncDialogState.delay,
lastFirstSyncDialogState.fromOutside,
lastFirstSyncDialogState.questionCounter,
lastFirstSyncDialogState.sourceIndex);
}
/**
* Contains the state of the last WI-Fi not available alert dialog request.
*/
class WifiNotAvailableDialogState {
protected String syncType;
protected Vector filteredSources;
protected int delay;
protected boolean refresh;
protected int direction;
protected boolean fromOutside;
/**
* Update this class fields with the current WIFINotAvailableDialogOption state
* @param appSourceList the list of appSyncSources
* @param syncType the String representation for the sync type
* (manual...)
* @param filteredSources the sources Vector to be updated in case the
* user select to sync now
* @param delay request the sync scheduler to be initiate the sync after
* the given amount of milliseconds
* @param fromOutside used by the sync scheduler to manage the incoming
* sync request from outside if true
* @param questionCounter the number of question to be displayed to the
* user. This number depends by the number of sources that have a
* warning messgae to be displayed at the first sync
* @param sourceIndex the sync source index source which this dialog
* alert is related.
*/
protected void update(String syncType,
Vector filteredSources, boolean refresh,
int direction, int delay, boolean fromOutside) {
this.syncType = syncType;
this.filteredSources = filteredSources;
this.refresh = refresh;
this.direction = direction;
this.delay = delay;
this.fromOutside = fromOutside;
}
}
/**
* Resumes the last WI-FI not available alert dialog if it was paused before the
* user answered. This could be due to any pausing event on the client side
* (screen rotation, incoming calls ...).
* This method works if and only if a previous call to the showNoWIFIAvailableDialog
* method was performed because it needs the last saved state of the field
* lastWifiNotAvailableDialogState that is updated by the showNoWIFIAvailableDialog method
* when called.
* @param screen the dialog alert owner Screen
*/
public void resumeWifiNotAvailableDialog(Screen screen) {
showNoWIFIAvailableDialog(screen,
lastWifiNotAvailableDialogState.syncType,
lastWifiNotAvailableDialogState.filteredSources,
lastWifiNotAvailableDialogState.refresh,
lastWifiNotAvailableDialogState.direction,
lastWifiNotAvailableDialogState.delay,
lastWifiNotAvailableDialogState.fromOutside);
}
/**
* Show the first sync alert dialogs for all the sources that are listed
* into the given appSourceList array.
* @param appSourceList the list of appSyncSources
* @param syncType the String representation for the sync type
* (manual...)
* @param filteredSources the sources Vector to be updated in case the
* user select to sync now
* @param refresh specifies if this sync is a refresh
* @param direction in case of refresh, this is the direction (client to
* server or server to client)
* @param delay request the sync scheduler to be initiate the sync after
* the given amount of milliseconds
* @param fromOutside used by the sync scheduler to manage the incoming
* sync request from outside if true
* @param questionCounter the number of question to be displayed to the
* user. This number depends by the number of sources that have a
* warning messgae to be displayed at the first sync
* @param sourceIndex the sync source index source which this dialog
* alert is related.
*/
public void showFirstSyncDialog(Screen screen, AppSyncSource[] appSourceList, String syncType,
Vector filteredSources, boolean refresh, int direction,
int delay, boolean fromOutside,
int questionCounter, int sourceIndex)
{
// This is just a safety check
if (sourceIndex >= appSourceList.length) {
Log.error(TAG_LOG, "Invalid source id, cannot show first sync dialog");
return;
}
//Set the last variables to be used in case of unexpected resume
//(for example a device screen rotation or a incoming call or in general
//an event that pauses the application and require a resume action)
String warning = appSourceList[sourceIndex].getWarningOnFirstSync();
FirstSyncDialogOption[] options = new FirstSyncDialogOption[2];
options[0] = new FirstSyncDialogOption(screen, localization.getLanguage("dialog_sync_now"), 0,
appSourceList, syncType, filteredSources, refresh,
direction, delay, fromOutside,
questionCounter, sourceIndex);
options[1] = new FirstSyncDialogOption(screen, localization.getLanguage("dialog_try_later"), 1,
appSourceList, syncType, filteredSources, refresh,
direction, delay, fromOutside,
questionCounter, sourceIndex);
lastFirstSyncDialogState.update(appSourceList, syncType, filteredSources,
refresh, direction, delay, fromOutside,
questionCounter, sourceIndex);
displayManager.promptSelection(screen, warning, options, 0, displayManager.FIRST_SYNC_DIALOG_ID);
}
/**
* Container for the first sync dialog options
*/
protected class FirstSyncDialogOption extends DialogOption {
private AppSyncSource[] appSourceList;
private String syncType;
private Vector filteredSources;
private boolean refresh;
private int direction;
private int delay;
private boolean fromOutside;
private int questionCounter;
private int sourceIndex;
/**
* Public copy-constructor
* @param screen the dialog alert owner Screen
* @param description the dialog option description (describe the
* current option to the user)
* @param value the current option returned value
* @param appSourceList the list of appSyncSources
* @param syncType the String representation for the sync type
* (manual...)
* @param filteredSources the sources Vector to be updated in case the
* user select to sync now
* @param refresh specifies if this sync is for a refresh
* @param direction in case of refresh sync, this is the direction
* (client to server or server to client)
* @param delay request the sync scheduler to be initiate the sync after
* the given amount of milliseconds
* @param fromOutside used by the sync scheduler to manage the incoming
* sync request from outside if true
* @param questionCounter the number of question to be displayed to the
* user. This number depends by the number of sources that have a
* warning messgae to be displayed at the first sync
* @param sourceIndex the sync source index source which this dialog
* alert is related.
*/
public FirstSyncDialogOption(
Screen screen,
String description,
int value,
AppSyncSource[] appSourceList,
String syncType,
Vector filteredSources,
boolean refresh,
int direction,
int delay,
boolean fromOutside,
int questionCounter,
int sourceIndex) {
super(screen, description, value);
this.appSourceList = appSourceList;
this.filteredSources = filteredSources;
this.refresh = refresh;
this.direction = direction;
this.delay = delay;
this.fromOutside = fromOutside;
this.syncType = syncType;
this.questionCounter = questionCounter;
this.sourceIndex = sourceIndex;
//Post the runnable action for the latest alert
Runnable cancelAction = null;
if (sourceIndex >= questionCounter - 1) {
displayManager.addPostDismissSelectionDialogAction(displayManager.FIRST_SYNC_DIALOG_ID, this);
}
}
/**
* Triggered in threaded like logic when the user selects an option.
*/
public void run() {
//Whenever an alert choice is selected by the user the related
//action related to the dismiss alert must be removed and must be
//redefined run through this thread
displayManager.removePostDismissSelectionDialogAction(displayManager.FIRST_SYNC_DIALOG_ID);
//Dismiss the first sync selection dialog passing the command to
//reset the sync all button on the home screen if and only if the
//dialog is the latest to be shown to the user. The dismiss action
//must rely on the DisplayManager implementation.
displayManager.dismissSelectionDialog(displayManager.FIRST_SYNC_DIALOG_ID);
HomeScreenController hsCont = controller.getHomeScreenController();
// We don't want to show the alert twice. Even if the user postpones
// the sync, we consider the source as synced just when nan active
// response has been given to the alert (yes/no). In case the alert
// is dismissed for any reason the client will display the alert
// next time the sync is requested.
appSourceList[sourceIndex].getConfig().setSynced(true);
appSourceList[sourceIndex].getConfig().commit();
if (getValue() == 0) {
//User says "Sync Now"
//add the given app source to the next sync request vector
filteredSources.addElement(appSourceList[sourceIndex]);
}
hsCont.redraw();
if (sourceIndex >= questionCounter - 1) {
if(!hsCont.isSynchronizing()) {
hsCont.changeSyncLabelsOnSyncEnded();
}
//Last sync question reached. Synchronization must start now. If
//the filteredSources param is empty, then the sync is
//terminated
hsCont.continueSynchronizationAfterFirstSyncDialog(syncType, filteredSources,
refresh, direction,
delay, fromOutside, true);
} else {
//There are more sources that require this alert to be displayed
sourceIndex++;
showFirstSyncDialog(screen, appSourceList, syncType, filteredSources,
refresh, direction, delay, fromOutside,
questionCounter, sourceIndex);
}
}
}
/**
* Container for the reset type dialog option
*/
protected class ResetTypeDialogOption extends DialogOption {
int direction;
/**
* Public copy-constructor
* @param screen the dialog alert owner Screen
* @param description the dialog option description (describe the
* current option to the user)
* @param value the current option returned value
* @param direction the related sync direction value
*/
public ResetTypeDialogOption(Screen screen, String description, int value, int direction) {
super(screen, description, value);
this.direction = direction;
}
/**
* Accessor method to get the sync direction value
* @return int the sync direction
*/
public int getDirection() {
return this.direction;
}
/**
* Triggered in threaded like logic when the user selects an option.
*/
public void run() {
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "TYPE - Selected: " + this.getDescription() + " - code " +
this.getValue() + " - direction " + getDirection());
}
//dismiss the progress dialog
displayManager.dismissSelectionDialog(displayManager.REFRESH_TYPE_DIALOG_ID);
if (!this.getDescription().equals(localization.getLanguage("dialog_cancel"))) {
//User selected the sync sources to be refreshed
try {
displayManager.hideScreen(screen);
//starts the sync
controller.getHomeScreenController().refresh(value, direction);
controller.getHomeScreenController().redraw();
} catch (Exception ex) {
Log.error("Exception accessing home screen: " + ex);
}
} else {
//User selected the cancel option
controller.getHomeScreenController().redraw();
}
}
}
}