content: package com.almalence.plugins.vf.gyro;
  
  import java.util.concurrent.atomic.AtomicBoolean;
  
  import android.content.Context;
  import android.content.SharedPreferences;
  import android.content.SharedPreferences.Editor;
  import android.content.res.Configuration;
  import android.graphics.Color;
  import android.graphics.PorterDuff.Mode;
  import android.hardware.Sensor;
  import android.hardware.SensorManager;
  import android.os.Handler;
  import android.preference.PreferenceManager;
  import android.view.Display;
  import android.view.LayoutInflater;
  import android.view.Surface;
  import android.view.View;
  import android.view.ViewGroup.LayoutParams;
  import android.view.WindowManager;
  import android.widget.RelativeLayout;
  
  import com.almalence.plugins.capture.panoramaaugmented.AugmentedRotationListener;
  import com.almalence.plugins.capture.panoramaaugmented.VfGyroSensor;
  import com.almalence.ui.RotateImageView;
  
  /* <!-- +++
  import com.almalence.opencam_plus.ApplicationScreen;
  import com.almalence.opencam_plus.PluginViewfinder;
  import com.almalence.opencam_plus.R;
  import com.almalence.opencam_plus.cameracontroller.CameraController;
  +++ --> */
  //<!-- -+-
  import com.almalence.opencam.ApplicationScreen;
  import com.almalence.opencam.PluginViewfinder;
  import com.almalence.opencam.R;
  import com.almalence.opencam.cameracontroller.CameraController;
  
  //-+- -->
  
  public class GyroVFPlugin extends PluginViewfinder
  {
  
  	private static final Boolean		ON						= true;
  	private static final Boolean		OFF						= false;
  
  	private int							mOrientation;
  	private Boolean						mGyroState;
  	private SensorManager				mSensorManager;
  	private Sensor						mMagnetometer;
  	private Sensor						mAccelerometer;
  	private Sensor						mGyroscope;
  	private VfGyroSensor				mVfGyroscope;
  	private AugmentedRotationListener	mAugmentedListener;
  	private AugmentedSurfaceView		mSurfacePreviewAugmented;
  
  	private static Handler				handler					= new Handler();
  
  	private float						viewAngleX				= 55.4f;
  	private float						viewAngleY				= 42.7f;
  
  	private int							pictureWidth;
  	private int							pictureHeight;
  
  	private int							previewWidth;
  	private int							previewHeight;
  
  	private boolean						remapOrientation;
  	private boolean						mPrefHardwareGyroscope	= true;
  
  	private RelativeLayout				mHorizonIndicatorContainer;
  	private RelativeLayout				mHorizonIndicatorMarkContainer;
  	private RotateImageView				mHorizonIndicatorMarkRotation;
  	private RotateImageView				mHorizonIndicatorMarkHorizontal;
  	private RotateImageView				mHorizonIndicatorMarkTopDown;
  	private RotateImageView				mHorizonIndicatorAim;
  	private RotateImageView				mHorizonIndicatorAimTopDown;
  	private View						mHorizonLayout;
  	private Boolean						flat					= false;
  
  	public GyroVFPlugin()
  	{
  		super("com.almalence.plugins.gyrovf", R.xml.preferences_vf_gyro, 0, R.drawable.gui_almalence_settings_gyro,
  				ApplicationScreen.getAppResources().getString(R.string.Pref_TitleGyroVF));
  	}
  
  	@Override
  	public void onCameraParametersSetup()
  	{
  		this.checkCoordinatesRemapRequired();
  
  		this.pictureWidth = CameraController.getCameraImageSize().getWidth();
  		this.pictureHeight = CameraController.getCameraImageSize().getHeight();
  
  		this.previewWidth = ApplicationScreen.getPreviewWidth();
  		this.previewHeight = ApplicationScreen.getPreviewHeight();
  
  		try
  		{
  			this.viewAngleX = CameraController.getHorizontalViewAngle();
  			this.viewAngleY = CameraController.getVerticalViewAngle();
  		} catch (final Exception e)
  		{
  			// Some bugged camera drivers pop ridiculous exception here, use
  			// typical view angles then
  			this.viewAngleX = 55.4f;
  			this.viewAngleY = 42.7f;
  		}
  
  		// some devices report incorrect FOV values, use typical view angles
  		// then
  		if (this.viewAngleX >= 150)
  		{
  			this.viewAngleX = 55.4f;
  			this.viewAngleY = 42.7f;
  		}
  
  		// Some cameras report incorrect view angles
  		// Usually vertical view angle is incorrect, but eg Htc One report
  		// incorrect horizontal view angle
  		// If aspect ratio from FOV differs by more than 10% from aspect ratio
  		// from W/H
  		// - re-compute view angle
  		float HorizontalViewFromAspect = 2
  				* 180
  				* (float) Math.atan((float) this.pictureWidth / (float) this.pictureHeight
  						* (float) Math.tan((float) Math.PI * this.viewAngleY / (2 * 180))) / (float) Math.PI;
  		float VerticalViewFromAspect = 2
  				* 180
  				* (float) Math.atan((float) this.pictureHeight / (float) this.pictureWidth
  						* (float) Math.tan((float) Math.PI * this.viewAngleX / (2 * 180))) / (float) Math.PI;
  		// not expecting very narrow field of view
  		if ((VerticalViewFromAspect > 40.f) && (VerticalViewFromAspect < 0.9f * this.viewAngleY))
  			this.viewAngleY = VerticalViewFromAspect;
  		else if ((HorizontalViewFromAspect < 0.9f * this.viewAngleX)
  				|| (HorizontalViewFromAspect > 1.1f * this.viewAngleX))
  			this.viewAngleX = HorizontalViewFromAspect;
  
  		if(mSurfacePreviewAugmented != null)
  			mSurfacePreviewAugmented.reset(this.pictureHeight, this.pictureWidth, this.viewAngleY);
  
  		if (!mPrefHardwareGyroscope)
  		{
  			mVfGyroscope.SetFrameParameters(this.previewWidth, this.previewHeight, this.viewAngleX, this.viewAngleY);
  		}
  	}
  
  	private void checkCoordinatesRemapRequired()
  	{
  		final Display display = ((WindowManager) ApplicationScreen.instance.getSystemService(Context.WINDOW_SERVICE))
  				.getDefaultDisplay();
  		// This is proved way of checking it so we better use deprecated
  		// methods.
  		@SuppressWarnings("deprecation")
  		final int orientation = (display.getWidth() <= display.getHeight()) ? Configuration.ORIENTATION_PORTRAIT
  				: Configuration.ORIENTATION_LANDSCAPE;
  		final int rotation = display.getRotation();
  		this.remapOrientation = (orientation == Configuration.ORIENTATION_LANDSCAPE && rotation == Surface.ROTATION_0)
  				|| (orientation == Configuration.ORIENTATION_LANDSCAPE && rotation == Surface.ROTATION_180)
  				|| (orientation == Configuration.ORIENTATION_PORTRAIT && rotation == Surface.ROTATION_90)
  				|| (orientation == Configuration.ORIENTATION_PORTRAIT && rotation == Surface.ROTATION_270);
  	}
  
  	@Override
  	public void onResume()
  	{
  		mSensorManager = (SensorManager) ApplicationScreen.instance.getSystemService(Context.SENSOR_SERVICE);
  		mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
  		mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
  		mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
  
  		// to see if it helps to fix "can't load library error"
  		try
  		{
  			mVfGyroscope = new VfGyroSensor(null);
  		} catch (Exception e)
  		{
  			e.printStackTrace();
  		}
  		mSurfacePreviewAugmented = new AugmentedSurfaceView(this);
  		updatePreferences();
  	}
  
  	public boolean needPreviewFrame()
  	{
  		if (mGyroState == ON)
  			return true;
  		else
  			return false;
  	}
  	
  	void updatePreferences()
  	{
  		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ApplicationScreen.getMainContext());
  		mGyroState = prefs.getBoolean("PrefGyroVF", false);
  
  		if (!prefs.contains("PrefGyroTypeVF"))
  		{
  			Editor editor = prefs.edit();
  			if (mGyroscope != null)
  			{
  				editor.putBoolean("PrefGyroTypeVF", true);
  			} else
  			{
  				editor.putBoolean("PrefGyroTypeVF", false);
  			}
  			editor.commit();
  		}
  		mPrefHardwareGyroscope = prefs.getBoolean("PrefGyroTypeVF", false);
  
  		if (mGyroState == ON)
  		{
  			quickControlIconID = R.drawable.gui_almalence_settings_gyro;
  			if (mHorizonIndicatorContainer != null)
  			{
  				mHorizonIndicatorContainer.setVisibility(View.VISIBLE);
  			}
  			initSensors();
  		} else
  		{
  			quickControlIconID = R.drawable.gui_almalence_settings_gyro_off;
  			if (mHorizonIndicatorContainer != null)
  			{
  				mHorizonIndicatorContainer.setVisibility(View.GONE);
  			}
  			releaseSensors();
  		}
  	}
  
  	@Override
  	public void onPreviewFrame(byte[] data)
  	{
  		if (mGyroState == OFF)
  			return;
  
  		if (!this.mPrefHardwareGyroscope)
  		{
  			this.mVfGyroscope.NewData(data);
  			if (mSurfacePreviewAugmented != null)
  			{
  				mSurfacePreviewAugmented.onDrawFrame();
  			}
  		}
  	}
  
  	@Override
  	public void onOrientationChanged(int orientation)
  	{
  		mOrientation = orientation;
  		if (mHorizonIndicatorMarkHorizontal != null)
  		{
  			mHorizonIndicatorMarkHorizontal.setRotation(orientation - 90);
  		}
  	}
  
  	@Override
  	public void onQuickControlClick()
  	{
  		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ApplicationScreen.getMainContext());
  		Editor editor = prefs.edit();
  
  		if (mGyroState == ON)
  		{
  			quickControlIconID = R.drawable.gui_almalence_settings_gyro_off;
  			editor.putBoolean("PrefGyroVF", false);
  		} else
  		{
  			quickControlIconID = R.drawable.gui_almalence_settings_gyro;
  			editor.putBoolean("PrefGyroVF", true);
  		}
  		editor.commit();
  
  		updatePreferences();
  		
  		CameraController.checkNeedPreviewFrame();
  	}
  
  	@Override
  	public void onPause()
  	{
  		releaseSensors();
  	}
  
  	private void updatehHardwareGyro()
  	{
  		if (mGyroState == OFF)
  			return;
  
  		if (mSurfacePreviewAugmented != null)
  		{
  			mSurfacePreviewAugmented.onDrawFrame();
  		}
  		handler.postDelayed(new Runnable()
  		{
  			@Override
  			public void run()
  			{
  				updatehHardwareGyro();
  			}
  		}, 50);
  	}
  
  	private void initSensors()
  	{
  		if (mGyroState == ON)
  		{
  			if (mSensorManager == null)
  			{
  				mSensorManager = (SensorManager) ApplicationScreen.instance.getSystemService(Context.SENSOR_SERVICE);
  			}
  			if (mSensorManager == null)
  			{
  				return;
  			}
  
  			mSurfacePreviewAugmented.reset(this.pictureHeight, this.pictureWidth, this.viewAngleY);
  
  			mAugmentedListener = new AugmentedRotationListener(remapOrientation, !mPrefHardwareGyroscope);
  
  			if (this.mGyroscope != null)
  			{
  				if (mPrefHardwareGyroscope)
  				{
  					mSensorManager.registerListener(mAugmentedListener, mGyroscope, SensorManager.SENSOR_DELAY_GAME);
  					updatehHardwareGyro();
  					handler.postDelayed(new Runnable()
  					{
  						@Override
  						public void run()
  						{
  							updatehHardwareGyro();
  						}
  					}, 500);
  				}
  			}
  
  			if (!mPrefHardwareGyroscope)
  			{
  				if (mVfGyroscope == null)
  				{
  					mVfGyroscope = new VfGyroSensor(null);
  				}
  				mVfGyroscope.open();
  				mVfGyroscope.SetListener(mAugmentedListener);
  			}
  
  			mSensorManager.registerListener(mAugmentedListener, mAccelerometer, SensorManager.SENSOR_DELAY_GAME);
  			mSensorManager.registerListener(mAugmentedListener, mMagnetometer, SensorManager.SENSOR_DELAY_GAME);
  
  			mAugmentedListener.setReceiver(mSurfacePreviewAugmented);
  		}
  	}
  
  	private void releaseSensors()
  	{
  		mGyroState = OFF;
  		if (mPrefHardwareGyroscope)
  		{
  			if (mSensorManager != null)
  				mSensorManager.unregisterListener(mAugmentedListener, mGyroscope);
  		} else
  		{
  			if (null != mVfGyroscope)
  				mVfGyroscope.SetListener(null);
  		}
  
  		if (mSensorManager != null)
  		{
  			mSensorManager.unregisterListener(mAugmentedListener, mAccelerometer);
  			mSensorManager.unregisterListener(mAugmentedListener, mMagnetometer);
  		}
  	}
  
  	@Override
  	public void onGUICreate()
  	{
  		createGyroUI();
  
  		if (mGyroState == ON)
  		{
  			if (mHorizonIndicatorContainer != null)
  			{
  				mHorizonIndicatorContainer.setVisibility(View.VISIBLE);
  			}
  		} else
  		{
  			if (mHorizonIndicatorContainer != null)
  			{
  				mHorizonIndicatorContainer.setVisibility(View.GONE);
  			}
  		}
  	}
  
  	private AtomicBoolean	horizon_updating	= new AtomicBoolean(false);
  
  	public void updateHorizonIndicator(float verticalError, float horizontalError, final float sideErrorVertical,
  			final float sideErrorHorizontal)
  	{
  
  		if (mHorizonIndicatorContainer == null || mHorizonIndicatorContainer == null
  				|| mHorizonIndicatorMarkTopDown == null || mHorizonIndicatorMarkRotation == null
  				|| mHorizonIndicatorMarkHorizontal == null || mHorizonIndicatorAim == null
  				|| mHorizonIndicatorAimTopDown == null || mHorizonIndicatorMarkContainer == null)
  		{
  			return;
  		}
  
  		if (ApplicationScreen.getGUIManager().lockControls)
  		{
  			mHorizonIndicatorContainer.setVisibility(View.GONE);
  			return;
  		}
  
  		mHorizonIndicatorContainer.setVisibility(View.VISIBLE);
  
  		if (!horizon_updating.compareAndSet(false, true))
  		{
  			return;
  		}
  
  		if (Math.abs(horizontalError) <= 0.01f && Math.abs(verticalError) <= 0.01f)
  		{
  			mHorizonIndicatorMarkContainer.setPadding(0, 0, 0, 0);
  			if (!flat)
  			{
  				rotateHorizonIndicator(sideErrorVertical, sideErrorHorizontal);
  			}
  
  			int color = 255;
  			mHorizonIndicatorMarkTopDown.setColorFilter(Color.rgb(color, color, 0), Mode.MULTIPLY);
  			mHorizonIndicatorMarkRotation.setColorFilter(Color.rgb(color, color, 0), Mode.MULTIPLY);
  
  			horizon_updating.set(false);
  			return;
  		}
  
  		float density = ApplicationScreen.getAppResources().getDisplayMetrics().density;
  		if ((Math.abs(horizontalError) > 1.0f && (mOrientation == 0 || mOrientation == 180 || flat))
  				|| (Math.abs(verticalError) > 1.0f && (mOrientation == 90 || mOrientation == 270 || flat)))
  		{
  			flat = true;
  			mHorizonIndicatorMarkRotation.setVisibility(View.GONE);
  			mHorizonIndicatorMarkHorizontal.setVisibility(View.GONE);
  			mHorizonIndicatorMarkTopDown.setVisibility(View.VISIBLE);
  			mHorizonIndicatorAim.setVisibility(View.GONE);
  			mHorizonIndicatorAimTopDown.setVisibility(View.VISIBLE);
  
  			if (Math.abs(verticalError) > 0.9f)
  			{
  				if (verticalError > 0.0f)
  				{
  					verticalError = (float) (verticalError - Math.PI / 2);
  				} else
  				{
  					verticalError = (float) (verticalError + Math.PI / 2);
  				}
  			}
  
  			if (Math.abs(horizontalError) > 0.9f)
  			{
  				if (horizontalError > 0.0f)
  				{
  					horizontalError = (float) (horizontalError - Math.PI / 2);
  				} else
  				{
  					horizontalError = (float) (horizontalError + Math.PI / 2);
  				}
  			}
  
  			final int marginVerticalValue = (int) (300.0f * Math.abs(verticalError) * density);
  			final int marginHorizontalValue = (int) (300.0f * Math.abs(horizontalError) * density);
  
  			int color = (255 - (marginVerticalValue + marginHorizontalValue) * 4);
  			if (color > 50)
  			{
  				mHorizonIndicatorMarkTopDown.setColorFilter(Color.rgb(color, color, 0), Mode.MULTIPLY);
  			} else
  			{
  				mHorizonIndicatorMarkTopDown.clearColorFilter();
  			}
  
  			if (verticalError < 0.0f)
  			{
  				if (horizontalError < 0.0f)
  				{
  					mHorizonIndicatorMarkContainer.setPadding(0, marginVerticalValue, marginHorizontalValue, 0);
  				} else
  				{
  					mHorizonIndicatorMarkContainer.setPadding(marginHorizontalValue, marginVerticalValue, 0, 0);
  				}
  			} else
  			{
  				if (horizontalError < 0.0f)
  				{
  					mHorizonIndicatorMarkContainer.setPadding(0, 0, marginHorizontalValue, marginVerticalValue);
  				} else
  				{
  					mHorizonIndicatorMarkContainer.setPadding(marginHorizontalValue, 0, 0, marginVerticalValue);
  				}
  			}
  			horizon_updating.set(false);
  		} else
  		{
  			flat = false;
  			mHorizonIndicatorMarkRotation.setVisibility(View.VISIBLE);
  			mHorizonIndicatorMarkHorizontal.setVisibility(View.VISIBLE);
  			mHorizonIndicatorMarkTopDown.setVisibility(View.GONE);
  			mHorizonIndicatorAim.setVisibility(View.VISIBLE);
  			mHorizonIndicatorAimTopDown.setVisibility(View.GONE);
  			final int marginVerticalValue = (int) (300.0f * Math.abs(verticalError) * density);
  			final int marginHorizontalValue = (int) (300.0f * Math.abs(horizontalError) * density);
  
  			rotateHorizonIndicator(sideErrorVertical, sideErrorHorizontal);
  
  			if (mOrientation == 90 || mOrientation == 270)
  			{
  				if (verticalError < 0.0f)
  				{
  					mHorizonIndicatorMarkContainer.setPadding(0, marginVerticalValue, 0, 0);
  				} else
  				{
  					mHorizonIndicatorMarkContainer.setPadding(0, 0, 0, marginVerticalValue);
  				}
  			} else
  			{
  				if (horizontalError < 0.0f)
  				{
  					mHorizonIndicatorMarkContainer.setPadding(0, 0, marginHorizontalValue, 0);
  				} else
  				{
  					mHorizonIndicatorMarkContainer.setPadding(marginHorizontalValue, 0, 0, 0);
  				}
  			}
  			horizon_updating.set(false);
  		}
  	}
  
  	private void rotateHorizonIndicator(float sideErrorVertical, float sideErrorHorizontal)
  	{
  		mHorizonIndicatorAim.setOrientation(mOrientation - 90);
  
  		int colorVert = (255 - Math.abs(((int) Math.toDegrees(sideErrorVertical) - 90) % 90) * 15);
  		int colorHorz = (255 - Math.abs(((int) Math.toDegrees(sideErrorHorizontal) - 90) % 90) * 15);
  
  		if (mOrientation == 90 || mOrientation == 270)
  		{
  			if (colorVert > 100)
  			{
  				mHorizonIndicatorMarkRotation.setColorFilter(Color.rgb(colorVert, colorVert, 0), Mode.MULTIPLY);
  			} else
  			{
  				mHorizonIndicatorMarkRotation.clearColorFilter();
  			}
  		} else
  		{
  			if (colorHorz > 100)
  			{
  				mHorizonIndicatorMarkRotation.setColorFilter(Color.rgb(colorHorz, colorHorz, 0), Mode.MULTIPLY);
  			} else
  			{
  				mHorizonIndicatorMarkRotation.clearColorFilter();
  			}
  		}
  
  		if (mOrientation == 90)
  		{
  			mHorizonIndicatorMarkRotation.setOrientation((int) Math.toDegrees(sideErrorVertical) + 180 + mOrientation);
  		}
  
  		if (mOrientation == 270)
  		{
  			mHorizonIndicatorMarkRotation.setOrientation(-(int) Math.toDegrees(sideErrorVertical) + mOrientation);
  		}
  
  		if (mOrientation == 180)
  		{
  			mHorizonIndicatorMarkRotation
  					.setOrientation((int) Math.toDegrees(sideErrorHorizontal) + 180 + mOrientation);
  		}
  
  		if (mOrientation == 0)
  		{
  			mHorizonIndicatorMarkRotation.setOrientation(-(int) Math.toDegrees(sideErrorHorizontal) + mOrientation);
  		}
  	}
  
  	private void createGyroUI()
  	{
  		LayoutInflater inflator = ApplicationScreen.instance.getLayoutInflater();
  		mHorizonLayout = inflator.inflate(R.layout.plugin_vf_gyro_layout, null, false);
  		mHorizonLayout.setVisibility(View.VISIBLE);
  
  		ApplicationScreen.getGUIManager().removeViews(mHorizonLayout, R.id.specialPluginsLayout);
  
  		RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
  				LayoutParams.WRAP_CONTENT);
  		((RelativeLayout) ApplicationScreen.instance.findViewById(R.id.specialPluginsLayout)).addView(mHorizonLayout,
  				params);
  
  		mHorizonIndicatorAim = (RotateImageView) mHorizonLayout.findViewById(R.id.horizon_indicator_aim);
  		mHorizonIndicatorAimTopDown = (RotateImageView) mHorizonLayout
  				.findViewById(R.id.horizon_indicator_aim_top_down);
  		mHorizonIndicatorMarkRotation = (RotateImageView) mHorizonLayout
  				.findViewById(R.id.horizon_indicator_mark_rotation);
  		mHorizonIndicatorMarkHorizontal = (RotateImageView) mHorizonLayout
  				.findViewById(R.id.horizon_indicator_mark_horizontal);
  		mHorizonIndicatorMarkTopDown = (RotateImageView) mHorizonLayout
  				.findViewById(R.id.horizon_indicator_mark_top_down);
  		mHorizonIndicatorContainer = (RelativeLayout) mHorizonLayout.findViewById(R.id.horizon_indicator_container);
  		mHorizonIndicatorMarkContainer = (RelativeLayout) mHorizonLayout
  				.findViewById(R.id.horizon_indicator_mark_container);
  	}
  }
  