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.transport.tcp;
  
  import java.io.ByteArrayInputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.net.Socket;
  import java.util.Hashtable;
  
  import android.content.Context;
  import android.os.PowerManager;
  
  import com.vodafone360.people.Settings;
  import com.vodafone360.people.datatypes.AuthSessionHolder;
  import com.vodafone360.people.engine.EngineManager;
  import com.vodafone360.people.engine.login.LoginEngine;
  import com.vodafone360.people.service.RemoteService;
  import com.vodafone360.people.service.transport.ConnectionManager;
  import com.vodafone360.people.service.transport.IWakeupListener;
  import com.vodafone360.people.service.transport.http.HttpConnectionThread;
  import com.vodafone360.people.service.utils.AuthUtils;
  import com.vodafone360.people.service.utils.hessian.HessianEncoder;
  import com.vodafone360.people.service.utils.hessian.HessianUtils;
  import com.vodafone360.people.service.io.rpg.RpgHeader;
  import com.vodafone360.people.service.io.rpg.RpgMessageTypes;
  
  /**
   * Sends heartbeats to the RPG in order to keep the connection alive.
   * 
   * @author Rudy Norff (rudy.norff@vodafone.com)
   */
  public class HeartbeatSenderThread implements Runnable, IWakeupListener {
      /**
       * The HeartbeatSenderThread that was created by the TcpConnectionThread. It
       * will be compared to this thread. If they differ this thread must be a
       * lock thread that got stuck when the user changed APNs or switched from
       * WiFi to another data connection type.
       */
      protected static HeartbeatSenderThread mCurrentThread;
  
      /**
       * The milliseconds in a second.
       */
      private static final long MILLIS_PER_SEC = 1000;
  
      /**
       * The service under which context the hb sender runs in. Used for setting
       * alarms that wake up the CPU for sending out heartbeats.
       */
      private RemoteService mService;
  
      /**
       * The managing thread that needs to be called back if an IOException occurs
       * sending a heartbeat.
       */
      private TcpConnectionThread mConnThread;
  
      /**
       * The thread continuously sending the heartbeats.
       */
      protected Thread mThread;
  
      /**
       * The output stream to write the heartbeat to.
       */
      protected OutputStream mOs;
  
      /**
       * This is the interval at which the heartbeat gets sent. The problem with
       * this is that this timeout interval is only proven in the Vodafone
       * network. In other networks we will do an autodetect for the correct
       * interval settings.
       */
      protected long mHeartbeatInterval;
  
      /**
       * Indicates that the connection is running if true.
       */
      private boolean mIsConnectionRunning;
  
      /**
       * Keeps a partial wake lock on the CPU that will prevent it from sleeping
       * and allow the Sender to send off heartbeats.
       */
      private PowerManager.WakeLock mWakeLock;
  
      /**
       * The socket to write to.
       */
      private Socket mSocket;
  
      /**
       * Constructs a heartbeat-sender and passes the connection thread to call
       * back to in case of errors.
       * 
       * @param connThread The connection thread to call back to in case of
       *            networking issues.
       * @param service The remote service that we register with once we have set
       *            the heartbeat alarm.
       */
      public HeartbeatSenderThread(TcpConnectionThread connThread, RemoteService service,
              Socket socket) {
          mConnThread = connThread;
          mHeartbeatInterval = Settings.TCP_VF_HEARTBEAT_INTERVAL;
  
          mService = service;
          mSocket = socket;
  
          if (null != mService) {
              mService.registerCpuWakeupListener(this);
              PowerManager pm = (PowerManager)mService.getSystemService(Context.POWER_SERVICE);
              mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                      "HeartbeatSenderThread.HeartbeatSenderThread()");
          }
      }
  
      /**
       * Sets the state of the connection to run and spawns a thread to run it in.
       */
      public synchronized void startConnection() {
          HttpConnectionThread.logI("RpgTcpHeartbeatSender.startConnection()", "STARTING HB Sender!");
  
          mIsConnectionRunning = true;
  
          mThread = null; // if we are restarting let's clear the last traces
          mThread = new Thread(this, "RpgTcpHeartbeatSender");
          mThread.start();
      }
  
      /**
       * Stops the heartbeat-senders connection and closes the output-stream to
       * the socket-connection.
       */
      public synchronized void stopConnection() {
          mIsConnectionRunning = false;
          synchronized (this) {
              notify();
          }
  
          mService.setAlarm(false, 0);
  
          if (null != mSocket) {
              try {
                  mSocket.shutdownOutput();
              } catch (IOException ioe) {
                  HttpConnectionThread.logE("RpgTcpHeartbeatSender.stopConnection()",
                          "Could not shutdown OutputStream", ioe);
              } finally {
                  mSocket = null;
              }
          }
  
          if (null != mOs) {
              try {
                  mOs.close();
              } catch (IOException ioe) {
                  HttpConnectionThread.logE("RpgTcpHeartbeatSender.stopConnection()",
                          "Could not close OutputStream", ioe);
              } finally {
                  mOs = null;
              }
          }
      }
  
      /**
       * Sets the output-stream so that the heartbeat-sender can send its
       * heartbeats to the RPG.
       * 
       * @param outputStream The open output-stream that the heartbeats shall be
       *            sent to. Avoid passing null as this will result in an
       *            error-callback to RpgTcpConnectionThread which will completely
       *            reestablish the socket connection.
       */
      public void setOutputStream(OutputStream outputStream) {
          HttpConnectionThread.logI("RpgTcpHeartbeatSender.setOutputStream()", "Setting new OS.");
          mOs = outputStream;
      }
  
      /**
       * The run-method overriding Thread.run(). The method continuously sends out
       * heartbeats by calling sendHeartbeat() and then waits for a
       * Thread.interrupt(), a notify or for the wait timer to time out after a
       * timer-value defined in Settings.TCP_VF_HEARTBEAT_INTERVAL.
       */
      public void run() {
          // sets the wakeup alarm at XX minutes. This is important as the CPU
          // gets woken
          // by this call if it should be in standby mode.
  
          while (mIsConnectionRunning) {
              try {
                  if (!this.equals(mCurrentThread)) {
                      // The current thread created by the TcpConnectionThread is
                      // not equal to this thread.
                      // This thread must be old (locked due to the user changing
                      // his network settings.
                      HttpConnectionThread.logE("HeartbeatSenderThread.run()", "This thread is a "
                              + "locked thread caused by the connection settings being switched.",
                              null);
                      stopConnection(); // exit this thread
                      break;
                  }
  
                  mWakeLock.acquire();
                  sendHeartbeat();
  
                  mService.setAlarm(true, (System.currentTimeMillis() + mHeartbeatInterval));
              } catch (IOException ioe) {
                  if ((null != mConnThread) && (mIsConnectionRunning)) {
                      mConnThread.notifyOfNetworkProblems();
                  }
                  HttpConnectionThread.logE("RpgTcpHeartbeatSender.run()", "Could not send HB!", ioe);
              } catch (Throwable t) {
                  if ((null != mConnThread) && (mIsConnectionRunning)) {
                      mConnThread.notifyOfNetworkProblems();
                  }
                  HttpConnectionThread.logE("RpgTcpHeartbeatSender.run()",
                          "Could not send HB! Unknown: ", t);
              } finally {
                  if (mWakeLock.isHeld()) {
                      mWakeLock.release();
                  }
              }
  
              synchronized (this) {
                  try {
                      wait(mHeartbeatInterval + (mHeartbeatInterval / 5));
                  } catch (InterruptedException e) {
                      HttpConnectionThread.logE("RpgTcpHeartbeatSender.run()", "Failed sleeping", e);
                  }
              }
          }
      }
  
      /**
       * Prepares the necessary Hessian payload and writes it directly to the open
       * output-stream of the socket.
       * 
       * @throws Exception Thrown if there was an unknown problem writing to the
       *             output-stream.
       * @throws IOException Thrown if there was a problem regarding IO while
       *             writing to the output-stream.
       */
      public void sendHeartbeat() throws IOException, Exception {
      	 byte[] rpgMsg = null;
      	try {
               rpgMsg = getHeartbeatHessianPayload();
      	} catch(NullPointerException e) {
      	    // Stop connnection and log out
     		 	ConnectionManager.getInstance().onLoginStateChanged(false);
     		 	EngineManager.getInstance().getLoginEngine().logoutAndRemoveUser();
      	}
  
          try {
              // Try and issue the request
              if (Settings.sEnableProtocolTrace) {
                  Long userID = null;
                  AuthSessionHolder auth = LoginEngine.getSession();
                  if (auth != null) {
                      userID = auth.userID;
                  }
                  
                  HttpConnectionThread.logI("RpgTcpHeartbeatSender.sendHeartbeat()",
                          "\n  * Sending a heartbeat for user ID "
                                  + userID + "----------------------------------------"
                                  + HessianUtils.getInHessian(new ByteArrayInputStream(rpgMsg), true)
                                  + "\n");
              }
  
              if (null != mOs) {
                  synchronized (mOs) {
                      mOs.write(rpgMsg);
                      mOs.flush();
                  }
              }
          } catch (IOException ioe) {
              HttpConnectionThread.logE("RpgTcpHeartbeatSender.sendHeartbeat()",
                      "Could not write HB to OS!", ioe);
              throw ioe;
          } catch (Exception e) {
              HttpConnectionThread.logE("RpgTcpHeartbeatSender.sendHeartbeat()",
                      "Could not send HB to OS! Unknown: ", e);
              throw e;
          } finally {
              rpgMsg = null;
          }
      }
  
      /**
       * Returns a byte-array containing the data needed for sending a heartbeat
       * to the RPG.
       * 
       * @throws IOException If there was an exception serializing the hash map to
       *             a hessian byte array.
       * @return A byte array representing the heartbeat.
       */
      private byte[] getHeartbeatHessianPayload() throws IOException {
          // hash table for parameters to Hessian encode
          final Hashtable<String, Object> ht = new Hashtable<String, Object>();
  
          final String timestamp = "" + ((long)System.currentTimeMillis() / MILLIS_PER_SEC);
          final AuthSessionHolder auth = LoginEngine.getSession();
          if(auth == null) {
          	throw new NullPointerException();
          }
          ht.put("auth", AuthUtils
                  .calculateAuth("", new Hashtable<String, Object>(), timestamp, auth));
          ht.put("userid", auth.userID);
  
          // do Hessian encoding
          final byte[] payload = HessianEncoder.createHessianByteArray("", ht);
          payload[1] = (byte)1;
          payload[2] = (byte)0;
  
          final int reqLength = RpgHeader.HEADER_LENGTH + payload.length;
  
          final RpgHeader rpgHeader = new RpgHeader();
          rpgHeader.setPayloadLength(payload.length);
          rpgHeader.setReqType(RpgMessageTypes.RPG_TCP_HEARTBEAT);
  
          final byte[] rpgMsg = new byte[reqLength];
          System.arraycopy(rpgHeader.createHeader(), 0, rpgMsg, 0, RpgHeader.HEADER_LENGTH);
  
          if (null != payload) {
              System.arraycopy(payload, 0, rpgMsg, RpgHeader.HEADER_LENGTH, payload.length);
          }
  
          return rpgMsg;
      }
  
      /**
       * <p>
       * Returns true if the heartbeat thread is currently running/active.
       * </p>
       * <p>
       * Calling startConnection() will make this method return as the heartbeat
       * thread gets started.
       * </p>
       * <p>
       * stopConnection() will stop the connection again and make this method
       * return false.
       * </p>
       * 
       * @return True if the connection is running, false otherwise.
       */
      public boolean getIsActive() {
          return mIsConnectionRunning;
      }
  
      @Override
      public void notifyOfWakeupAlarm() {
          if (Settings.sEnableProtocolTrace) {
              HttpConnectionThread.logI("HeartbeatSenderThread.notifyOfWakeupAlarm()",
                      "Waking up for a heartbeat!");
          }
  
          synchronized (this) {
              notify(); // this will try to send another heartbeat
          }
      }
  }
  