/*
* 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");
}
}