/* * CDDL HEADER START * * The contents of this file are subject to the terms of the Common Development * and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at * src/com/vodafone360/people/VODAFONE.LICENSE.txt or * http://github.com/360/360-Engine-for-Android * See the License for the specific language governing permissions and * limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each file and * include the License file at src/com/vodafone360/people/VODAFONE.LICENSE.txt. * If applicable, add the following below this CDDL HEADER, with the fields * enclosed by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * Copyright 2010 Vodafone Sales & Services Ltd. All rights reserved. * Use is subject to license terms. */ package com.vodafone360.people.engine; import java.security.InvalidParameterException; import java.util.HashMap; import java.util.Iterator; import java.util.Set; import com.vodafone360.people.MainApplication; import com.vodafone360.people.Settings; import com.vodafone360.people.SettingsManager; import com.vodafone360.people.engine.activities.ActivitiesEngine; import com.vodafone360.people.engine.contactsync.ContactSyncEngine; import com.vodafone360.people.engine.content.ContentEngine; import com.vodafone360.people.engine.groups.GroupsEngine; import com.vodafone360.people.engine.identities.IdentityEngine; import com.vodafone360.people.engine.login.LoginEngine; import com.vodafone360.people.engine.meprofile.SyncMeEngine; import com.vodafone360.people.engine.presence.PresenceEngine; import com.vodafone360.people.engine.upgrade.UpgradeEngine; import com.vodafone360.people.service.RemoteService; import com.vodafone360.people.service.WorkerThread; import com.vodafone360.people.service.io.QueueManager; import com.vodafone360.people.service.io.ResponseQueue; import com.vodafone360.people.service.transport.ConnectionManager; import com.vodafone360.people.utils.LogUtils; /** * EngineManager class is responsible for creating, handling and deletion of * engines in the People client. The EngineManager determine when each engine * should be run based on the engine's next run time or whether there is a * waiting request for that engine. The EngineManager routes received responses * to the appropriate engine. */ public class EngineManager { /** * Identifiers for engines. */ public enum EngineId { LOGIN_ENGINE, CONTACT_SYNC_ENGINE, GROUPS_ENGINE, ACTIVITIES_ENGINE, IDENTITIES_ENGINE, PRESENCE_ENGINE, UPGRADE_ENGINE, CONTENT_ENGINE, SYNCME_ENGINE, UNDEFINED // add ids as we progress } /** * {@link EngineManager} is a singleton, so this is the static reference. */ private static EngineManager sEngineManager; /** * Engine manager maintains a list of all engines in the system. This is a * map between the engine ID and the engine reference. */ private final HashMap<Integer, BaseEngine> mEngineList = new HashMap<Integer, BaseEngine>(); /** * Reference to the {@RemoteService} object which provides * access to the {@link WorkerThread}. */ private RemoteService mService; /** * Engines require access the {@link IEngineEventCallback} interface. * Implements several useful methods for engines such as UI request * complete. */ private IEngineEventCallback mUiEventCallback; /** * @see LoginEngine */ private LoginEngine mLoginEngine; /** * @see UpgradeEngine */ private UpgradeEngine mUpgradeEngine; /** * @see ActivitiesEngine */ private ActivitiesEngine mActivitiesEngine; /** * @see SyncMeEngine */ private SyncMeEngine mSyncMeEngine; /** * @see PresenceEngine */ private PresenceEngine mPresenceEngine; /** * @see IdentityEngine */ private IdentityEngine mIdentityEngine; /** * @see ContactSyncEngine */ private ContactSyncEngine mContactSyncEngine; /** * @see GroupsEngine */ private GroupsEngine mGroupsEngine; /** * @see ContentEngine */ private ContentEngine mContentEngine; /** * Maximum time the run function for an engine is allowed to run before a * warning message will be displayed (debug only) */ private static final long ENGINE_RUN_TIME_THRESHOLD = 3000; /** * Engine Manager Constructor * * @param service {@link RemoteService} reference * @param uiCallback Provides useful engine callback functionality. */ private EngineManager(RemoteService service, IEngineEventCallback uiCallback) { mService = service; mUiEventCallback = uiCallback; } /** * Create instance of EngineManager. * * @param service {@link RemoteService} reference * @param uiCallback Provides useful engine callback functionality. */ public static void createEngineManager(RemoteService service, IEngineEventCallback uiCallback) { sEngineManager = new EngineManager(service, uiCallback); sEngineManager.onCreate(); } /** * Destroy EngineManager. */ public static void destroyEngineManager() { if (sEngineManager != null) { sEngineManager.onDestroy(); sEngineManager = null; } } /** * Get single instance of {@link EngineManager}. * * @return {@link EngineManager} singleton instance. */ public static EngineManager getInstance() { if (sEngineManager == null) { throw new InvalidParameterException("Please call EngineManager.createEngineManager() " + "before EngineManager.getInstance()"); } return sEngineManager; } /** * Add a new engine to the EngineManager. * * @param newEngine Engine to be added. */ private synchronized void addEngine(BaseEngine newEngine) { final String newName = newEngine.getClass().getSimpleName(); String[] deactivatedEngines = SettingsManager .getStringArrayProperty(Settings.DEACTIVATE_ENGINE_LIST_KEY); for (String engineName : deactivatedEngines) { if (engineName.equals(newName)) { LogUtils.logW("DEACTIVATE ENGINE: " + engineName); newEngine.deactivateEngine(); } } if (!newEngine.isDeactivated()) { newEngine.onCreate(); mEngineList.put(newEngine.mEngineId.ordinal(), newEngine); } mService.kickWorkerThread(); } /** * Closes an engine and removes it from the list. * * @param engine Reference of engine by base class {@link BaseEngine} to * close */ private synchronized void closeEngine(BaseEngine engine) { mEngineList.remove(engine.engineId().ordinal()); if (!engine.isDeactivated()) { engine.onDestroy(); } } /** * Called immediately after manager has been created. Starts the necessary * engines */ private synchronized void onCreate() { // LogUtils.logV("EngineManager.onCreate()"); createLoginEngine(); createIdentityEngine(); createSyncMeEngine(); createContactSyncEngine(); createGroupsEngine(); if (SettingsManager.getProperty(Settings.UPGRADE_CHECK_URL_KEY) != null) { createUpgradeEngine(); } createActivitiesEngine(); createPresenceEngine(); createContentEngine(); } /** * Called just before the service is stopped. Shuts down all the engines */ private synchronized void onDestroy() { final int engineCount = mEngineList.values().size(); BaseEngine[] engineList = new BaseEngine[engineCount]; mEngineList.values().toArray(engineList); for (int i = 0; i < engineCount; i++) { closeEngine(engineList[i]); } mLoginEngine = null; mUpgradeEngine = null; mActivitiesEngine = null; mPresenceEngine = null; mIdentityEngine = null; mContactSyncEngine = null; mGroupsEngine = null; mContentEngine = null; } /** * Obtains a reference to the login engine * * @return a reference to the LoginEngine */ public LoginEngine getLoginEngine() { assert mLoginEngine != null; return mLoginEngine; } /** * Create instance of LoginEngine. */ private synchronized void createLoginEngine() { final MainApplication app = (MainApplication)mService.getApplication(); mLoginEngine = new LoginEngine(mService, mUiEventCallback, app.getDatabase()); addEngine(mLoginEngine); } /** * Fetch upgrade engine * * @return UpgradeEngine object */ public UpgradeEngine getUpgradeEngine() { assert mUpgradeEngine != null; return mUpgradeEngine; } /** * Create instance of UpgradeEngine. */ private synchronized void createUpgradeEngine() { mUpgradeEngine = new UpgradeEngine(mService, mUiEventCallback); addEngine(mUpgradeEngine); } /** * Fetch activities engine * * @return a ActivitiesEngine object */ public ActivitiesEngine getActivitiesEngine() { assert mActivitiesEngine != null; return mActivitiesEngine; } /** * Create instance of ActivitiesEngine. */ private synchronized void createActivitiesEngine() { final MainApplication app = (MainApplication)mService.getApplication(); mActivitiesEngine = new ActivitiesEngine(mService, mUiEventCallback, app.getDatabase()); getLoginEngine().addListener(mActivitiesEngine); addEngine(mActivitiesEngine); } /** * Fetch sync me engine, starting it if necessary. * * @return The SyncMeEngine */ public SyncMeEngine getSyncMeEngine() { assert mSyncMeEngine != null; return mSyncMeEngine; } /** * Create instance of SyncMeEngine. */ private synchronized void createSyncMeEngine() { final MainApplication app = (MainApplication)mService.getApplication(); mSyncMeEngine = new SyncMeEngine(mService, mUiEventCallback, app.getDatabase()); addEngine(mSyncMeEngine); } /** * Fetch presence engine * * @return Presence Engine object */ public PresenceEngine getPresenceEngine() { assert mPresenceEngine != null; return mPresenceEngine; } /** * Fetch identity engine * * @return IdentityEngine object */ public IdentityEngine getIdentityEngine() { assert mIdentityEngine != null; return mIdentityEngine; } /** * Fetch content engine * * @return ContentEngine object */ public ContentEngine getContentEngine() { assert mContentEngine != null; return mContentEngine; } /** * Fetch contact sync engine * * @return ContactSyncEngine object */ public ContactSyncEngine getContactSyncEngine() { assert mContactSyncEngine != null; return mContactSyncEngine; } private synchronized void createContactSyncEngine() { final MainApplication app = (MainApplication)mService.getApplication(); mContactSyncEngine = new ContactSyncEngine(mService, mUiEventCallback, app.getDatabase(), null); addEngine(mContactSyncEngine); } /** * Fetch groups engine * * @return GroupEngine object */ public GroupsEngine getGroupsEngine() { assert mGroupsEngine != null; return mGroupsEngine; } private synchronized void createGroupsEngine() { final MainApplication app = (MainApplication)mService.getApplication(); mGroupsEngine = new GroupsEngine(mService, mUiEventCallback, app.getDatabase()); addEngine(mGroupsEngine); } /** * Create instance of IdentityEngine. */ private synchronized void createIdentityEngine() { final MainApplication app = (MainApplication)mService.getApplication(); mIdentityEngine = new IdentityEngine(mUiEventCallback, app.getDatabase()); ConnectionManager.getInstance().addConnectionListener(mIdentityEngine); addEngine(mIdentityEngine); } /** * Create instance of ContentEngine. */ private synchronized void createContentEngine() { final MainApplication app = (MainApplication)mService.getApplication(); mContentEngine = new ContentEngine(mUiEventCallback, app.getDatabase()); addEngine(mContentEngine); } /** * Create instance of PresenceEngine. */ private synchronized void createPresenceEngine() { final MainApplication app = (MainApplication)mService.getApplication(); mPresenceEngine = new PresenceEngine(mUiEventCallback, app.getDatabase()); ConnectionManager.getInstance().addConnectionListener(mPresenceEngine); getLoginEngine().addListener(mPresenceEngine); addEngine(mPresenceEngine); } /** * Respond to incoming message received from Comms layer. If this message * has a valid engine id it is routed to that engine, otherwise The * {@link EngineManager} will try to get the next response. * * @param source EngineId associated with incoming message. */ public void onCommsInMessage(EngineId source) { BaseEngine engine = null; if (source != null) { engine = mEngineList.get(source.ordinal()); } if (engine != null) { engine.onCommsInMessage(); } else { LogUtils.logE("EngineManager.onCommsInMessage - " + "Cannot dispatch message, unknown source " + source); final ResponseQueue queue = ResponseQueue.getInstance(); queue.getNextResponse(source); } } /** * Run any waiting engines and return the time in milliseconds from now when * this method needs to be called again. * * @return -1 never needs to run, 0 needs to run as soon as possible, * CurrentTime + 60000 in 1 minute, etc. */ public synchronized long runEngines() { long nextRuntime = -1; Set<Integer> e = mEngineList.keySet(); Iterator<Integer> i = e.iterator(); while (i.hasNext()) { int engineId = i.next(); BaseEngine engine = mEngineList.get(engineId); long currentTime = System.currentTimeMillis(); // TODO: Pass mCurrentTime to getNextRunTime() to help with Unit tests long tempRuntime = engine.getNextRunTime(); if (Settings.ENABLED_ENGINE_TRACE) { LogUtils.logV("EngineManager.runEngines() " + "engine[" + engine.getClass().getSimpleName() + "] " + "nextRunTime[" + getHumanReadableTime(tempRuntime, currentTime) + "] " + "current[" + getHumanReadableTime(nextRuntime, currentTime) + "]"); } else { if (tempRuntime > 0 && tempRuntime < currentTime) { LogUtils.logD("Engine[" + engine.getClass().getSimpleName() + "] run pending"); } } if (tempRuntime < 0) { if (Settings.ENABLED_ENGINE_TRACE) { LogUtils.logV("EngineManager.runEngines() Engine is off, so ignore"); } } else if (tempRuntime <= currentTime) { if (Settings.ENABLED_ENGINE_TRACE) { LogUtils.logV("EngineManager.runEngines() Run Engine [" + engine.getClass().getSimpleName() + "] and make sure we check it once more before sleeping"); } /** TODO: Consider passing mCurrentTime to mEngine.run(). **/ engine.run(); nextRuntime = 0; final long timeForRun = System.currentTimeMillis() - currentTime; if (timeForRun > ENGINE_RUN_TIME_THRESHOLD) { LogUtils.logE("EngineManager.runEngines() Engine [" + engine.getClass().getSimpleName() + "] took " + timeForRun + "ms to run"); } if (Settings.ENABLED_PROFILE_ENGINES) { StringBuilder string = new StringBuilder(); string.append(System.currentTimeMillis()); string.append("|"); string.append(engine.getClass().getSimpleName()); string.append("|"); string.append(timeForRun); LogUtils.profileToFile(string.toString()); } } else { if (nextRuntime != -1) { nextRuntime = Math.min(nextRuntime, tempRuntime); } else { nextRuntime = tempRuntime; } if (Settings.ENABLED_ENGINE_TRACE) { LogUtils.logV("EngineManager.runEngines() Set mNextRuntime to [" + getHumanReadableTime(nextRuntime, currentTime) + "]"); } } } if (Settings.ENABLED_ENGINE_TRACE) { LogUtils.logI("EngineManager.getNextRunTime() Return [" + getHumanReadableTime(nextRuntime, System.currentTimeMillis()) + "]"); } return nextRuntime; } /*** * Display the Absolute Time in a human readable format (for testing only). * * @param absoluteTime Time to convert * @param currentTime Current time, for creating all relative times * @return Absolute time in human readable form */ private static String getHumanReadableTime(long absoluteTime, long currentTime) { if (absoluteTime == -1) { return "OFF"; } else if ((absoluteTime == 0)) { return "NOW"; } else if (absoluteTime >= currentTime) { return (absoluteTime - currentTime) + "ms"; } else { return (currentTime - absoluteTime) + "ms LATE"; } } /** * Resets all the engines. Note: the method will block until all the engines * have performed the reset. */ public void resetAllEngines() { LogUtils.logV("EngineManager.resetAllEngines() - begin"); synchronized (this) { // Entering this block should guarantee that the engines are not running // Propagate the reset event to all engines for (BaseEngine engine : mEngineList.values()) { engine.onReset(); } // Reset engine requests inside this synchronized block to // prevent running the engines at the same time QueueManager.getInstance().clearAllRequests(); } LogUtils.logV("EngineManager.resetAllEngines() - end"); } }