/* * 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.service; import java.security.InvalidParameterException; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import com.vodafone360.people.MainApplication; import com.vodafone360.people.Settings; import com.vodafone360.people.SettingsManager; import com.vodafone360.people.engine.EngineManager; import com.vodafone360.people.engine.contactsync.NativeContactsApi; import com.vodafone360.people.service.agent.NetworkAgent; import com.vodafone360.people.service.interfaces.IConnectionManagerInterface; import com.vodafone360.people.service.interfaces.IWorkerThreadControl; import com.vodafone360.people.service.transport.ConnectionManager; import com.vodafone360.people.service.transport.IWakeupListener; import com.vodafone360.people.service.utils.UserDataProtection; import com.vodafone360.people.utils.LogUtils; import com.vodafone360.people.utils.VersionUtils; /** * Implementation of People client's Service class. Loads properties from * SettingsManager. Creates NetworkAgent. Connects to ConnectionManager enabling * transport layer. Activates service's worker thread when required. */ public class RemoteService extends Service implements IWorkerThreadControl, IConnectionManagerInterface { /** * Intent received when service is started is tested against the value * stored in this key to determine if the service has been started because * of an alarm. */ public static final String ALARM_KEY = "alarm_key"; /** * Action for an Authenticator. * Straight copy from AccountManager.ACTION_AUTHENTICATOR_INTENT in 2.X platform */ public static final String ACTION_AUTHENTICATOR_INTENT = "android.accounts.AccountAuthenticator"; /** * Sync Adapter System intent action received on Bind. */ public static final String ACTION_SYNC_ADAPTER_INTENT = "android.content.SyncAdapter"; /** * Main reference to network agent * * @see NetworkAgent */ private NetworkAgent mNetworkAgent; /** * Worker thread reference * * @see WorkerThread */ private WorkerThread mWorkerThread; /** * The following object contains the implementation of the * {@link com.vodafone360.people.service.interfaces.IPeopleService} * interface. */ private PeopleServiceImpl mIPeopleServiceImpl; /** * Used by comms when waking up the CPI at regular intervals and sending a * heartbeat is necessary */ private IWakeupListener mWakeListener; /** * true when the service has been fully initialised */ private boolean mIsStarted = false; /** * Stores the previous network connection state (true = connected) */ private boolean mIsConnected = true; private NativeAccountObjectsHolder mAccountsObjectsHolder = null; /** * Creation of RemoteService. Loads properties (i.e. supported features, * server URLs etc) from SettingsManager. Creates IPeopleServiceImpl, * NetworkAgent. Connects ConnectionManager creating Connection thread(s) * and DecoderThread 'Kicks' worker thread. */ @Override public void onCreate() { LogUtils.logV("RemoteService.onCreate()"); SettingsManager.loadProperties(this); mIPeopleServiceImpl = new PeopleServiceImpl(this, this); mNetworkAgent = new NetworkAgent(this, this, this); // Create NativeContactsApi here to access Application Context NativeContactsApi.createInstance(getApplicationContext()); EngineManager.createEngineManager(this, mIPeopleServiceImpl); mNetworkAgent.onCreate(); mIPeopleServiceImpl.setNetworkAgent(mNetworkAgent); /** The service has now been fully initialised. **/ mIsStarted = true; kickWorkerThread(); final MainApplication mainApp = (MainApplication)getApplication(); mainApp.setServiceInterface(mIPeopleServiceImpl); if(VersionUtils.is2XPlatform()) { mAccountsObjectsHolder = new NativeAccountObjectsHolder(((MainApplication)getApplication())); } final UserDataProtection userDataProtection = new UserDataProtection(this, mainApp.getDatabase()); userDataProtection.performStartupChecks(); } /** * Called on start of RemoteService. Check if we need to kick the worker * thread or 'wake' the TCP connection thread. */ @Override public void onStart(Intent intent, int startId) { if(intent == null) { LogUtils.logV("RemoteService.onStart() intent is null. Returning."); return; } final Bundle bundle = intent.getExtras(); LogUtils.logI("RemoteService.onStart() Intent action[" + intent.getAction() + "] data[" + bundle + "]"); if ((null == bundle) || (null == bundle.getString(ALARM_KEY))) { LogUtils.logV("RemoteService.onStart() mBundle is null. Returning."); return; } if (bundle.getString(ALARM_KEY).equals(WorkerThread.ALARM_WORKER_THREAD)) { LogUtils.logV("RemoteService.onStart() ALARM_WORKER_THREAD Alarm thrown"); kickWorkerThread(); } else if (bundle.getString(ALARM_KEY).equals(IWakeupListener.ALARM_HB_THREAD)) { LogUtils.logV("RemoteService.onStart() ALARM_HB_THREAD Alarm thrown"); if (null != mWakeListener) { mWakeListener.notifyOfWakeupAlarm(); } } } /** * Destroy RemoteService Close WorkerThread, destroy EngineManger and * NetworkAgent. */ @Override public void onDestroy() { LogUtils.logV("RemoteService.onDestroy()"); ((MainApplication)getApplication()).setServiceInterface(null); mIsStarted = false; synchronized (this) { if (mWorkerThread != null) { mWorkerThread.close(); } } EngineManager.destroyEngineManager(); // No longer need NativeContactsApi NativeContactsApi.destroyInstance(); mNetworkAgent.onDestroy(); } /** * Service binding is not used internally by this Application, but called * externally by the system when it needs an Authenticator or Sync * Adapter. This method will throw an InvalidParameterException if it is * not called with the expected intent (or called on a 1.x platform). */ @Override public IBinder onBind(Intent intent) { final String action = intent.getAction(); if (VersionUtils.is2XPlatform() && action != null) { if (action.equals(ACTION_AUTHENTICATOR_INTENT)) { return mAccountsObjectsHolder.getAuthenticatorBinder(); } else if (action.equals(ACTION_SYNC_ADAPTER_INTENT)) { return mAccountsObjectsHolder.getSyncAdapterBinder(); } } throw new InvalidParameterException("RemoteService.action() " + "There are no Binders for the given Intent"); } /*** * Ensures that the WorkerThread runs at least once. */ @Override public void kickWorkerThread() { synchronized (this) { if (!mIsStarted) { // Thread will be kicked anyway once we have finished // initialisation. return; } if (mWorkerThread == null || !mWorkerThread.wakeUp()) { LogUtils.logV("RemoteService.kickWorkerThread() Start thread"); mWorkerThread = new WorkerThread(mHandler); mWorkerThread.start(); } } } /*** * Handler for remotely calling the kickWorkerThread() method. */ private final Handler mHandler = new Handler() { /** * Process kick worker thread message */ @Override public void handleMessage(Message msg) { kickWorkerThread(); } }; /** * Called by NetworkAgent to notify whether device has become connected or * disconnected. The ConnectionManager connects or disconnects * appropriately. We kick the worker thread if our internal connection state * is changed. * * @param connected true if device has become connected, false if device is * disconnected. */ @Override public void signalConnectionManager(boolean connected) { // if service agent becomes connected start conn mgr thread LogUtils.logI("RemoteService.signalConnectionManager()" + "Signalling Connection Manager to " + (connected ? "connect." : "disconnect.")); if (connected) { ConnectionManager.getInstance().connect(this); } else {// SA is disconnected stop conn thread (and close connections?) ConnectionManager.getInstance().disconnect(); } // kick EngineManager to run() and apply CONNECTED/DISCONNECTED changes if (connected != mIsConnected) { if (mIPeopleServiceImpl != null) { mIPeopleServiceImpl.kickWorkerThread(); } } mIsConnected = connected; } /** * <p> * Registers a listener (e.g. the HeartbeatSender for TCP) that will be * notified whenever an intent for a new alarm is received. * </p> * <p> * This is desperately needed as the CPU of Android devices will halt when * the user turns off the screen and all CPU related activity is suspended * for that time. The wake up alarm is one simple way of achieving the CPU * to wake up and send out data (e.g. the heartbeat sender). * </p> */ public void registerCpuWakeupListener(IWakeupListener wakeListener) { mWakeListener = wakeListener; } /** * Return handle to {@link NetworkAgent} * * @return handle to {@link NetworkAgent} */ public NetworkAgent getNetworkAgent() { return mNetworkAgent; } /** * Set an Alarm with the AlarmManager to trigger the next update check. * * @param set Set or cancel the Alarm * @param realTime Time when the Alarm should be triggered */ public void setAlarm(boolean set, long realTime) { Intent mRemoteServiceIntent = new Intent(this, this.getClass()); mRemoteServiceIntent.putExtra(RemoteService.ALARM_KEY, IWakeupListener.ALARM_HB_THREAD); PendingIntent mAlarmSender = PendingIntent.getService(this, 0, mRemoteServiceIntent, 0); AlarmManager mAlarmManager = (AlarmManager)getSystemService(RemoteService.ALARM_SERVICE); if (set) { if (Settings.ENABLED_ENGINE_TRACE) { LogUtils.logV("WorkerThread.setAlarm() Check for the next update at [" + realTime + "] or in [" + (realTime - System.currentTimeMillis()) + "ms]"); } mAlarmManager.set(AlarmManager.RTC_WAKEUP, realTime, mAlarmSender); /* * mAlarmManager.set(AlarmManager.RTC, realTime, mAlarmSender); * TODO: Optimisation suggestion - Consider only doing work when the * device is already awake */ } else { mAlarmManager.cancel(mAlarmSender); } } }