content: /*
  The contents of this file are subject to the Mozilla Public License
  Version 1.1 (the "License"); you may not use this file except in
  compliance with the License. You may obtain a copy of the License at
  http://www.mozilla.org/MPL/
  
  Software distributed under the License is distributed on an "AS IS"
  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
  License for the specific language governing rights and limitations
  under the License.
  
  The Original Code is collection of files collectively known as Open Camera.
  
  The Initial Developer of the Original Code is Almalence Inc.
  Portions created by Initial Developer are Copyright (C) 2013 
  by Almalence Inc. All Rights Reserved.
   */
  
  package com.almalence.plugins.capture.panoramaaugmented;
  
  // Usage:
  // - create in activity onCreate
  // - call SetFrameParameters just before starting camera preview
  // - call NewData(data) from onPreviewFrame
  // - call close() from activity onDestroy
  
  import java.io.Closeable;
  import java.lang.reflect.Constructor;
  
  import android.hardware.Sensor;
  import android.hardware.SensorEvent;
  import android.hardware.SensorEventListener;
  import android.hardware.SensorManager;
  import android.os.Handler;
  import android.os.Message;
  
  public class VfGyroSensor implements Closeable, Handler.Callback
  {
  	private static final boolean	SMOOTH_MOTION		= true;
  	private static final boolean	EARLY_TIMESTAMP		= true; // false;
  
  	// custom VF GYRO sensor type
  	public static final int			TYPE_VF_GYROSCOPE	= Sensor.TYPE_GYROSCOPE | 0x01000000;
  
  	public String getName()
  	{
  		return "ViewFinder Gyro";
  	}
  
  	public String getVendor()
  	{
  		return "Almalence";
  	}
  
  	public int getType()
  	{
  		return TYPE_VF_GYROSCOPE;
  	}
  
  	public int getVersion()
  	{
  		return 1;
  	}
  
  	public float getMaximumRange()
  	{
  		return 2 * (float) Math.PI;
  	}// full circle in a second
  
  	public float getResolution()
  	{
  		return 1e-5f;
  	}
  
  	public float getPower()
  	{
  		return 0;
  	}
  
  	public int getMinDelay()
  	{
  		return 16000;
  	} // ~ 60 fps
  
  	int getHandle()
  	{
  		return 0;
  	}
  
  	Constructor<SensorEvent>	EventConstructor	= null;
  	SensorEvent					sensorEvent, sensorEventPrev;
  	private float[]				sensorValuesPrev	= new float[3];
  
  	SensorEventListener			m_listener;
  
  	private boolean				m_justStability;
  
  	private byte[]				datacopy;
  	private long				timestamp;
  	private long				timestamp_initial;
  	private VfGyroSensor		mThiz;
  	private boolean				doneWithNewData		= true;
  	private int					nBlankRuns;
  
  	private final Handler		H					= new Handler(this);
  	private static final int	MSG_SMOOTHER_GYRO	= 1;
  
  	public VfGyroSensor(SensorEventListener listener)
  	{
  		mThiz = this;
  
  		doneWithNewData = true;
  
  		Initialize();
  
  		m_listener = listener;
  		m_justStability = false;
  
  		sensorValuesPrev[0] = sensorValuesPrev[1] = sensorValuesPrev[2] = 0;
  		nBlankRuns = 0;
  
  		// A hack to construct SensorEvent
  		try
  		{
  			EventConstructor = SensorEvent.class.getDeclaredConstructor(int.class); // Parameter
  																					// is
  																					// the
  																					// type
  																					// of
  																					// params
  																					// in
  																					// the
  																					// constructor
  																					// (e.g.
  																					// String)
  			EventConstructor.setAccessible(true);
  			sensorEvent = EventConstructor.newInstance(3);
  			sensorEventPrev = EventConstructor.newInstance(3);
  
  			sensorEvent.accuracy = SensorManager.SENSOR_STATUS_ACCURACY_HIGH;
  			sensorEvent.sensor = (Sensor) null;
  			sensorEventPrev.accuracy = SensorManager.SENSOR_STATUS_ACCURACY_HIGH;
  			sensorEventPrev.sensor = (Sensor) null;
  		} catch (Exception e)
  		{
  			e.printStackTrace();
  		}
  	}
  
  	public void open()
  	{
  		Initialize();
  	}
  
  	@Override
  	public void close() // throws IOException
  	{
  		Release();
  	}
  
  	@Override
  	public void finalize() throws Throwable
  	{
  		this.close();
  
  		super.finalize();
  	}
  
  	public void SetListener(SensorEventListener listener)
  	{
  		m_listener = listener;
  	}
  
  	public void SetStabilityOnly(boolean justStability)
  	{
  		m_justStability = justStability;
  	}
  
  	public boolean handleMessage(Message msg)
  	{
  		if (msg.what == MSG_SMOOTHER_GYRO)
  			NewData(null);
  
  		return true;
  	}
  
  	public void NewData(byte[] data)
  	{
  		if (EARLY_TIMESTAMP)
  			timestamp = System.nanoTime();
  
  		synchronized (sensorEventPrev)
  		{
  			// For smoother GUI: if frame is still in processing - pass the
  			// previous values,
  			// but take this (timestamp, values) into account for the next
  			// calculation
  			// Simpler way: if not finished with previous frame - do not accept
  			// the new one
  			if (SMOOTH_MOTION && ((!doneWithNewData) || (data == null)))
  			{
  				if (nBlankRuns < 4)
  				{
  					sensorEventPrev.timestamp = System.nanoTime(); // timestamp;
  
  					++nBlankRuns;
  
  					// emit sensor event
  					if (m_listener != null)
  					{
  						m_listener.onSensorChanged(sensorEventPrev);
  						H.sendEmptyMessageDelayed(MSG_SMOOTHER_GYRO, getMinDelay() / 1000);
  					}
  				}
  
  				return;
  			}
  		}
  
  		if ((!doneWithNewData) || (data == null))
  			return;
  
  		if (datacopy == null)
  			datacopy = new byte[data.length];
  		else if (datacopy.length != data.length)
  			datacopy = new byte[data.length];
  
  		System.arraycopy(data, 0, datacopy, 0, data.length);
  
  		doneWithNewData = false;
  
  		if (EARLY_TIMESTAMP)
  		{
  			sensorEvent.timestamp = timestamp;
  		}
  
  		// To prevent pressure on UI part - run gyro processing in separate
  		// thread
  		new Thread(new Runnable()
  		{
  			@Override
  			public void run()
  			{
  				synchronized (mThiz)
  				{
  					Update(datacopy, sensorEvent.timestamp, m_justStability);
  
  					Get(sensorEvent.values);
  
  					// clean any pending blank-run messages
  					if (SMOOTH_MOTION)
  						H.removeMessages(MSG_SMOOTHER_GYRO);
  
  					float[] savedValues = new float[3];
  
  					savedValues[0] = sensorEvent.values[0];
  					savedValues[1] = sensorEvent.values[1];
  					savedValues[2] = sensorEvent.values[2];
  
  					synchronized (sensorEventPrev)
  					{
  						if (!EARLY_TIMESTAMP)
  							sensorEvent.timestamp = System.nanoTime();
  
  						// if there were blank runs - correct for accumulated
  						// error
  						if (sensorEventPrev.timestamp != timestamp_initial)
  						{
  							long dt1 = sensorEvent.timestamp - timestamp_initial;
  							long dt2 = sensorEventPrev.timestamp - timestamp_initial;
  
  							if (dt1 != dt2) // replace with dt1 > dt2
  							{
  								float norm = 1.f / (dt1 - dt2);
  
  								for (int i = 0; i < 3; ++i)
  								{
  									float dx1 = dt1 * sensorEvent.values[i];
  									float dx2 = dt2 * sensorEventPrev.values[i];
  
  									sensorEvent.values[i] = (dx1 - dx2) * norm;
  								}
  							}
  						}
  
  						for (int i = 0; i < 3; ++i)
  						{
  							if (savedValues[i] * sensorValuesPrev[i] <= 0)
  								sensorEventPrev.values[i] = 0;
  							else if (Math.abs(savedValues[i]) < Math.abs(sensorValuesPrev[i]))
  								sensorEventPrev.values[i] = savedValues[i];
  							else
  								sensorEventPrev.values[i] = sensorValuesPrev[i];
  
  							sensorValuesPrev[i] = savedValues[i];
  						}
  
  						sensorEventPrev.timestamp = sensorEvent.timestamp;
  						timestamp_initial = sensorEvent.timestamp;
  						nBlankRuns = 0;
  
  						// emit sensor event
  						if (m_listener != null)
  						{
  							m_listener.onSensorChanged(sensorEvent);
  							if (SMOOTH_MOTION && (!m_justStability))
  								H.sendEmptyMessageDelayed(MSG_SMOOTHER_GYRO, getMinDelay() / 1000);
  						}
  
  						doneWithNewData = true;
  					}
  				}
  			}
  		}).start();
  	}
  
  	public native void Initialize();
  
  	public native void Release();
  
  	public native void SetFrameParameters(int w, int h, float HorizontalFOV, float VerticalFOV);
  
  	public native void Update(byte[] data, long timestamp, boolean justStability);
  
  	public native long Get(float[] values); // return value is timestamp
  
  	public static native void FixDrift(float[] values, boolean updateDrift);
  
  	static
  	{
  		System.loadLibrary("utils-image");
  		System.loadLibrary("almalib");
  		System.loadLibrary("almashot-pano");
  	}
  }
  