Java tutorial
/* * Copyright (C) 2014 Language In Interaction * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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 General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package nl.mpi.tg.eg.experiment.client; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JsArray; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.user.client.History; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window.ClosingEvent; import nl.mpi.tg.eg.experiment.client.listener.AppEventListner; import com.google.gwt.user.client.ui.RootLayoutPanel; import java.util.logging.Logger; import nl.mpi.tg.eg.experiment.client.listener.AudioExceptionListner; import nl.mpi.tg.eg.experiment.client.presenter.Presenter; import nl.mpi.tg.eg.experiment.client.presenter.ErrorPresenter; import nl.mpi.tg.eg.experiment.client.ApplicationController.ApplicationState; import nl.mpi.tg.eg.experiment.client.exception.AudioException; import nl.mpi.tg.eg.experiment.client.exception.DataSubmissionException; import nl.mpi.tg.eg.experiment.client.exception.UserIdException; import nl.mpi.tg.eg.experiment.client.listener.DataSubmissionListener; import nl.mpi.tg.eg.experiment.client.model.DataSubmissionResult; import nl.mpi.tg.eg.experiment.client.model.MetadataField; import nl.mpi.tg.eg.experiment.client.model.UserData; import nl.mpi.tg.eg.experiment.client.model.UserId; import nl.mpi.tg.eg.experiment.client.model.UserResults; import nl.mpi.tg.eg.experiment.client.presenter.StorageFullPresenter; import nl.mpi.tg.eg.experiment.client.presenter.TestingVersionPresenter; import nl.mpi.tg.eg.experiment.client.service.DataSubmissionService; import nl.mpi.tg.eg.experiment.client.service.LocalStorage; import nl.mpi.tg.eg.experiment.client.service.MetadataFieldProvider; import nl.mpi.tg.eg.experiment.client.service.TimerService; /** * @since Oct 7, 2014 11:07:35 AM (creation date) * @author Peter Withers <p.withers@psych.ru.nl> */ public abstract class AppController implements AppEventListner/*, AudioExceptionListner*/ { protected static final Logger logger = Logger.getLogger(AppController.class.getName()); private final Version version = GWT.create(Version.class); protected final Messages messages = GWT.create(Messages.class); final LocalStorage localStorage = new LocalStorage(messages.appNameInternal()); final DataSubmissionService submissionService = new DataSubmissionService(localStorage); final TimerService timerService = new TimerService(); protected final RootLayoutPanel widgetTag; protected Presenter presenter; protected final UserResults userResults; final MetadataFieldProvider metadataFieldProvider = new MetadataFieldProvider(); final boolean isDebugMode; public AppController(RootLayoutPanel widgetTag, String userIdGetParam) throws UserIdException { this.widgetTag = widgetTag; boolean obfuscationDisabled = false; String debugValue = Window.Location.getParameter("debug"); if (debugValue != null && !submissionService.isProductionVersion()) { localStorage.disableObfuscation(); obfuscationDisabled = true; isDebugMode = true; } else { isDebugMode = false; } final UserId lastUserId = localStorage.getLastUserId(userIdGetParam); if (lastUserId == null) { userResults = new UserResults(new UserData()); // we save the results here so that the newly created user id is preserved even if the user refreshes localStorage.storeData(userResults, metadataFieldProvider); } else { userResults = new UserResults(localStorage.getStoredData(lastUserId, metadataFieldProvider)); } if (obfuscationDisabled) { submissionService.submitScreenChange(userResults.getUserData().getUserId(), "obfuscationDisabled"); } boolean hasNewMetadata = false; for (MetadataField metadataField : metadataFieldProvider.metadataFieldArray) { final String postName = metadataField.getPostName(); String value = Window.Location.getParameter(postName); if (value != null) { userResults.getUserData().setMetadataValue(metadataField, value); hasNewMetadata = true; } } if (hasNewMetadata) { // todo: should we transmit this change here localStorage.storeData(userResults, metadataFieldProvider); } if (debugValue != null) { try { localStorage.saveAppState(userResults.getUserData().getUserId(), ApplicationState.valueOf("about")); } catch (IllegalArgumentException iae) { // there is not "about" presenter so we have nothing to do here but show the version localStorage.saveAppState(userResults.getUserData().getUserId(), ApplicationState.version); } } if (Window.Location.getParameter("version") != null) { localStorage.saveAppState(userResults.getUserData().getUserId(), ApplicationState.version); } // detectWindowDefocus(widgetTag); } final protected void preventWindowClose(final String messageString) { // on page close, back etc. provide a warning that their session will be invalide and they will not be paid etc. Window.addWindowClosingHandler(new Window.ClosingHandler() { @Override public void onWindowClosing(ClosingEvent event) { event.setMessage(messageString); } }); // on page close, back etc. send a screen event to the server Window.addCloseHandler(new CloseHandler<Window>() { @Override public void onClose(CloseEvent<Window> event) { submissionService.submitScreenChange(userResults.getUserData().getUserId(), "BrowserWindowClosed"); presenter.fireWindowClosing(); } }); } // private void detectWindowDefocus(RootLayoutPanel widgetTag) { // // this will most likely not work on a non input tag, however we are interested in stats on cases where it does // widgetTag.addHandler(new BlurHandler() { // // @Override // public void onBlur(BlurEvent event) { // submissionService.submitScreenChange(userResults.getUserData().getUserId(), "widgetTag.onBlur"); // } // }, BlurEvent.getType()); // widgetTag.addHandler(new FocusHandler() { // // @Override // public void onFocus(FocusEvent event) { // submissionService.submitScreenChange(userResults.getUserData().getUserId(), "widgetTag.onFocus"); // } // }, FocusEvent.getType()); // } // @Override // public void audioExceptionFired(AudioException audioException) { // logger.warning(audioException.getMessage()); // this.presenter = new ErrorPresenter(widgetTag, audioException.getMessage()); // presenter.setState(this, ApplicationState.start, null); // } abstract boolean preserveLastState(); public void start() { setBackButtonAction(); History.addValueChangeHandler(new ValueChangeHandler<String>() { @Override public void onValueChange(ValueChangeEvent<String> event) { if (preserveLastState()) { // when the navigation is blocked a new item is added over the last back event // this prevents both back and forward history actions triggering the back action, however no forward navigation will available unless the back action is to hide/show the stimuli menu, in which case it is ok History.newItem(localStorage.getAppState(userResults.getUserData().getUserId()), false); backAction(); } else if (event != null) { presenter.savePresenterState(); try { // this allows the browser navigation buttons to control the screen shown ApplicationState lastAppState = ApplicationState.valueOf(event.getValue()); requestApplicationState(lastAppState); } catch (IllegalArgumentException argumentException) { } } } }); Window.addWindowClosingHandler(new Window.ClosingHandler() { @Override public void onWindowClosing(ClosingEvent event) { presenter.savePresenterState(); submissionService.submitAllData(userResults, new DataSubmissionListener() { @Override public void scoreSubmissionFailed(DataSubmissionException exception) { } @Override public void scoreSubmissionComplete(JsArray<DataSubmissionResult> highScoreData) { } }); } }); try { submissionService.submitScreenChange(userResults.getUserData().getUserId(), "ApplicationStarted"); // application specific information submissionService.submitTagValue(userResults.getUserData().getUserId(), "ApplicationStarted", "projectVersion", version.projectVersion(), 0); submissionService.submitTagValue(userResults.getUserData().getUserId(), "ApplicationStarted", "lastCommitDate", version.lastCommitDate().replace("\"", ""), 0); submissionService.submitTagValue(userResults.getUserData().getUserId(), "ApplicationStarted", "compileDate", version.compileDate(), 0); submissionService.submitTagValue(userResults.getUserData().getUserId(), "ApplicationStarted", "navigator.platform", Window.Navigator.getPlatform(), 0); submissionService.submitTagValue(userResults.getUserData().getUserId(), "ApplicationStarted", "navigator.userAgent", Window.Navigator.getUserAgent(), 0); submissionService.submitTagValue(userResults.getUserData().getUserId(), "ApplicationStarted", "navigator.cookieEnabled", Boolean.toString(Window.Navigator.isCookieEnabled()), 0); submissionService.submitTagValue(userResults.getUserData().getUserId(), "ApplicationStarted", "storageLength", Integer.toString(localStorage.getStorageLength()), 0); if (hasCordova()) { // cordova specific information submissionService.submitTagValue(userResults.getUserData().getUserId(), "ApplicationStarted", "cordovaVersion", getCordovaVersion(), 0); submissionService.submitTagValue(userResults.getUserData().getUserId(), "ApplicationStarted", "deviceModel", getDeviceModel(), 0); submissionService.submitTagValue(userResults.getUserData().getUserId(), "ApplicationStarted", "devicePlatform", getDevicePlatform(), 0); submissionService.submitTagValue(userResults.getUserData().getUserId(), "ApplicationStarted", "deviceUUID", getDeviceUUID(), 0); submissionService.submitTagValue(userResults.getUserData().getUserId(), "ApplicationStarted", "deviceVersion", getDeviceVersion(), 0); } ApplicationState lastAppState = ApplicationState.start; try { final String appState = localStorage.getAppState(userResults.getUserData().getUserId()); // if the app state is preserved, then only the last saved state is used lastAppState = (appState != null) ? ApplicationState.valueOf(appState) : lastAppState; } catch (IllegalArgumentException argumentException) { } if (!preserveLastState() || isDebugMode /* checking for debug mode here and allowing presenter navigation here if true */) { // if the history token is valid then that is used otherwise the last saved or the start states are used final String token = History.getToken(); if (token != null) { try { submissionService.submitScreenChange(userResults.getUserData().getUserId(), "usingHistoryToken"); // this allows the URL to control the screen shown lastAppState = ApplicationState.valueOf(token); } catch (IllegalArgumentException argumentException) { } } } if (!submissionService.isProductionVersion()) { this.presenter = new TestingVersionPresenter(widgetTag, lastAppState); presenter.setState(this, null, null); } else { requestApplicationState(lastAppState); } addKeyboardEvents(); } catch (Exception exception) { this.presenter = new StorageFullPresenter(widgetTag, exception.getMessage()); presenter.setState(this, ApplicationState.start, null); } } public void backAction() { presenter.fireBackEvent(); } public void resizeAction() { presenter.fireResizeEvent(); } private native void setBackButtonAction() /*-{ var appController = this; $doc.addEventListener("backbutton", function(e){ e.preventDefault(); appController.@nl.mpi.tg.eg.experiment.client.AppController::backAction()(); }, false); }-*/; private native void addKeyboardEvents() /*-{ var appController = this; if($wnd.Keyboard) { $wnd.Keyboard.onshow = function () { $doc.getElementById("platformTag").innerHTML = "Keyboard.onshow GWT called"; appController.@nl.mpi.tg.eg.experiment.client.AppController::resizeAction()(); } $wnd.Keyboard.onhide = function () { $doc.getElementById("platformTag").innerHTML = "Keyboard.onhide GWT called"; appController.@nl.mpi.tg.eg.experiment.client.AppController::resizeAction()(); } } }-*/; protected native void exitApplication() /*-{ $doc.navigator.app.exitApp(); }-*/; protected native boolean hasCordova() /*-{ if ($wnd.device) return true; else return false; }-*/; protected native String getCordovaVersion() /*-{ return $wnd.device.cordova; }-*/; protected native String getDevicePlatform() /*-{ return $wnd.device.platform; }-*/; protected native String getDeviceUUID() /*-{ return $wnd.device.uuid; }-*/; protected native String getDeviceVersion() /*-{ return $wnd.device.version; }-*/; protected native String getDeviceModel() /*-{ return $wnd.device.model; }-*/; }