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