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.opencam_plus;
   +++ --> */
  //<!-- -+-
  package com.almalence.opencam;
  
  //-+- -->
  
  import java.io.ByteArrayInputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.util.ArrayList;
  import java.util.Enumeration;
  import java.util.HashMap;
  import java.util.Hashtable;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  
  import javax.microedition.khronos.egl.EGLConfig;
  import javax.microedition.khronos.opengles.GL10;
  
  import org.xmlpull.v1.XmlPullParserException;
  
  import android.annotation.TargetApi;
  import android.app.Activity;
  import android.content.ContentValues;
  import android.content.Intent;
  import android.content.SharedPreferences;
  import android.content.SharedPreferences.Editor;
  import android.graphics.ImageFormat;
  import android.graphics.Rect;
  import android.hardware.Camera;
  import android.hardware.camera2.CaptureResult;
  import android.location.Location;
  import android.media.ExifInterface;
  import android.opengl.GLSurfaceView;
  import android.os.Build;
  import android.os.Message;
  import android.preference.PreferenceFragment;
  import android.preference.PreferenceManager;
  import android.provider.MediaStore;
  import android.provider.MediaStore.Images;
  import android.provider.MediaStore.Images.ImageColumns;
  import android.support.v4.provider.DocumentFile;
  import android.view.KeyEvent;
  import android.view.MotionEvent;
  import android.view.View;
  import android.widget.RelativeLayout;
  import android.widget.Toast;
  
  import com.almalence.plugins.export.standard.GPSTagsConverter;
  import com.almalence.util.MLocation;
  import com.almalence.util.Util;
  import com.almalence.util.exifreader.imaging.jpeg.JpegMetadataReader;
  import com.almalence.util.exifreader.imaging.jpeg.JpegProcessingException;
  import com.almalence.util.exifreader.metadata.Directory;
  import com.almalence.util.exifreader.metadata.Metadata;
  import com.almalence.util.exifreader.metadata.exif.ExifIFD0Directory;
  import com.almalence.util.exifreader.metadata.exif.ExifSubIFDDirectory;
  
  /* <!-- +++
   import com.almalence.opencam_plus.cameracontroller.CameraController;
   +++ --> */
  //<!-- -+-
  import com.almalence.opencam.cameracontroller.CameraController;
  //-+- -->
  
  /***
   * Plugins managing class.
   * 
   * Controls plugins interaction with ApplicationScreen and processing, controls
   * different stages of activity workflow
   * 
   * may be used by other plugins to retrieve some parameters/settings from other
   * plugins
   ***/
  
  abstract public class PluginManagerBase implements PluginManagerInterface
  {
  
  	// we need some selection of active plugins by type.
  	// probably different lists for different plugin's types
  	// + probably it's more useful to have map instead of list (Map<Integer,
  	// Plugin> pluginList)
  	// in map case we'll have all plugins in one map and keys of active plugins
  	// of each type (as we have limited amount
  	// of types we can have just simple int variables or create a list, but it's
  	// more complicated)
  	protected Map<String, Plugin>				pluginList;
  	protected Map<Long, Plugin>					processingPluginList;
  
  	// active plugins IDs
  	protected List<String>						activeVF;
  	protected String							activeCapture;
  	protected String							activeProcessing;
  	// protected List<String> activeFilter;
  	protected String							activeExport;
  
  	// list of plugins by type
  	protected List<Plugin>						listVF;
  	protected List<Plugin>						listCapture;
  	protected List<Plugin>						listProcessing;
  	// protected List<Plugin> listFilter;
  	protected List<Plugin>						listExport;
  
  	// counter indicating amout of processing tasks running
  	protected int								cntProcessing			= 0;
  
  	// table for sharing plugin's data
  	// hashtable for storing shared data - assoc massive for string key and
  	// string value
  	// file SharedMemory.txt contains data keys and formats for currently used
  	// data
  	protected Hashtable<String, String>			sharedMemory;
  	protected Hashtable<String, CaptureResult>	rawCaptureResults;
  
  	// Support flag to avoid plugin's view disappearance issue
  	protected static boolean					isRestarting			= false;
  
  	static int									jpegQuality				= 95;
  
  	protected static boolean					isDefaultsSelected		= false;
  
  	protected static Map<Integer, Integer>		exifOrientationMap;
  
  	protected int								saveOption;
  
  	// plugin manager ctor. plugins initialization and filling plugin list
  	protected PluginManagerBase()
  	{
  		pluginList = new Hashtable<String, Plugin>();
  		processingPluginList = new HashMap<Long, Plugin>();
  
  		sharedMemory = new Hashtable<String, String>();
  
  		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
  			createRAWCaptureResultHashtable();
  
  		activeVF = new ArrayList<String>();
  
  		listVF = new ArrayList<Plugin>();
  		listCapture = new ArrayList<Plugin>();
  		listProcessing = new ArrayList<Plugin>();
  		listExport = new ArrayList<Plugin>();
  
  		createPlugins();
  
  		// parsing configuration file to setup modes
  		parseConfig();
  
  		exifOrientationMap = new HashMap<Integer, Integer>()
  		{
  			{
  				put(0, ExifInterface.ORIENTATION_NORMAL);
  				put(90, ExifInterface.ORIENTATION_ROTATE_90);
  				put(180, ExifInterface.ORIENTATION_ROTATE_180);
  				put(270, ExifInterface.ORIENTATION_ROTATE_270);
  			}
  		};
  	}
  
  	abstract protected void createPlugins();
  
  	@TargetApi(21)
  	public void createRAWCaptureResultHashtable()
  	{
  		rawCaptureResults = new Hashtable<String, CaptureResult>();
  	}
  
  	public void setupDefaultMode()
  	{
  		// select default mode - selection from preferences if exists. or from
  		// config if first start
  		Mode mode = getMode();
  
  		// when old mode removed for example
  		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ApplicationScreen.getMainContext());
  		if (mode == null)
  		{
  			// set default mode - get this val from mode.xml and later control
  			// in preerences
  			mode = ConfigParser.getInstance().getDefaultMode();
  
  			Editor prefsEditor = prefs.edit();
  			prefsEditor.putString(ApplicationScreen.sDefaultModeName, mode.modeID);
  			prefsEditor.commit();
  		}
  
  		// set active plugins for default mode
  		activeVF.clear();
  		for (int i = 0; i < mode.VF.size(); i++)
  			activeVF.add(mode.VF.get(i));
  		activeCapture = mode.Capture;
  		activeProcessing = mode.Processing;
  		activeExport = mode.Export;
  	}
  
  	public String getActiveModeID()
  	{
  		Mode mode = getActiveMode();
  		if (mode != null)
  			return mode.modeID;
  		return "";
  	}
  
  	public Mode getActiveMode()
  	{
  		// select default mode - selection from preferences if exists. or from
  		// config if first start
  		return getMode();
  	}
  
  	protected Mode getMode()
  	{
  		Mode mode = null;
  
  		// checks preferences
  		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ApplicationScreen.getMainContext());
  		if (prefs.contains(ApplicationScreen.sDefaultModeName))
  		{
  			String defaultModeName = prefs.getString(ApplicationScreen.sDefaultModeName, "");
  			mode = ConfigParser.getInstance().getMode(defaultModeName);
  		} else
  		{
  			// set default mode - get this val from mode.xml and later control
  			// in preerences
  			mode = ConfigParser.getInstance().getDefaultMode();
  
  			Editor prefsEditor = prefs.edit();
  			prefsEditor.putString(ApplicationScreen.sDefaultModeName, mode.modeID);
  			prefsEditor.commit();
  		}
  
  		return mode;
  	}
  
  	// isFromMain - indicates event originator - applicationscreen or processing
  	// screen
  	public void onCreate()
  	{
  		isDefaultsSelected = false;
  
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).onCreate();
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onCreate();
  		if (null != pluginList.get(activeProcessing))
  			pluginList.get(activeProcessing).onCreate();
  		if (null != pluginList.get(activeExport))
  			pluginList.get(activeExport).onCreate();
  
  		onManagerCreate();
  	}
  
  	abstract void onManagerCreate();
  
  	// parse config to get camera and modes configurations
  	void parseConfig()
  	{
  		try
  		{
  			ConfigParser.getInstance().parse(ApplicationScreen.getMainContext());
  		} catch (XmlPullParserException e)
  		{
  			e.printStackTrace();
  		} catch (IOException e)
  		{
  			e.printStackTrace();
  		}
  	}
  
  	//shows if camera has to be restarted on mode change
  	protected boolean	isRestart					= false;
  	protected boolean	switchToOldCameraInterface	= false;
  
  	//indicate is restart of camera needed on mode switch
  	public void setSwitchModeType(boolean restart)
  	{
  		isRestart = restart;
  	}
  
  	public boolean isRestart()
  	{
  		return isRestart;
  	}
  
  	public boolean isSwitchToOldCameraInterface()
  	{
  		return switchToOldCameraInterface;
  	}
  
  	public void switchMode(final Mode mode)
  	{
  		String modeName = mode.modeID;
  		if (CameraController.isNexus6 && (modeName.equals("panorama_augmented") || modeName.equals("preshot")))
  			switchToOldCameraInterface = true;
  		else
  			switchToOldCameraInterface = false;
  
  		// disable old plugins
  		ApplicationScreen.getGUIManager().onStop();
  		ApplicationScreen.instance.switchingMode(isRestart ? false : true);
  		ApplicationScreen.instance.pauseMain();
  		onStop();
  		onDestroy();
  
  		// clear lists and fill with new active plugins
  		activeVF.clear();
  		for (int i = 0; i < mode.VF.size(); i++)
  			activeVF.add(mode.VF.get(i));
  		activeCapture = mode.Capture;
  		activeProcessing = mode.Processing;
  		activeExport = mode.Export;
  
  		// set mode as default for future starts
  		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ApplicationScreen.getMainContext());
  		Editor prefsEditor = prefs.edit();
  		prefsEditor.putString(ApplicationScreen.sDefaultModeName, mode.modeID);
  		prefsEditor.commit();
  
  		onCreate();
  		onStart();
  		ApplicationScreen.instance.switchingMode(isRestart ? false : true);
  		ApplicationScreen.instance.resumeMain();
  	}
  
  	public List<Plugin> getActivePlugins(PluginType type)
  	{
  		List<Plugin> activePlugins = new ArrayList<Plugin>();
  		switch (type)
  		{
  		case ViewFinder:
  			{
  				for (int i = 0; i < activeVF.size(); i++)
  					activePlugins.add(pluginList.get(activeVF.get(i)));
  			}
  			break;
  		case Capture:
  			activePlugins.add(pluginList.get(activeCapture));
  			break;
  		case Processing:
  			activePlugins.add(pluginList.get(activeProcessing));
  			break;
  		case Export:
  			activePlugins.add(pluginList.get(activeExport));
  			break;
  		default:
  			break;
  		}
  
  		return activePlugins;
  	}
  
  	// base onStart stage
  	public void onStart()
  	{
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).onStart();
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onStart();
  		if (null != pluginList.get(activeProcessing))
  			pluginList.get(activeProcessing).onStart();
  		if (null != pluginList.get(activeExport))
  			pluginList.get(activeExport).onStart();
  	}
  
  	// base onStop stage;
  	public void onStop()
  	{
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).onStop();
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onStop();
  		if (null != pluginList.get(activeProcessing))
  			pluginList.get(activeProcessing).onStop();
  		if (null != pluginList.get(activeExport))
  			pluginList.get(activeExport).onStop();
  	}
  
  	// base onDestroy stage
  	public void onDestroy()
  	{
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).onDestroy();
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onDestroy();
  		if (null != pluginList.get(activeProcessing))
  			pluginList.get(activeProcessing).onDestroy();
  		if (null != pluginList.get(activeExport))
  			pluginList.get(activeExport).onDestroy();
  	}
  
  	// base onResume stage
  	public void onResume()
  	{
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).onResume();
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onResume();
  		if (null != pluginList.get(activeProcessing))
  			pluginList.get(activeProcessing).onResume();
  		if (null != pluginList.get(activeExport))
  			pluginList.get(activeExport).onResume();
  	}
  
  	// base onPause stage
  	public void onPause(boolean isFromMain)
  	{
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).onPause();
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onPause();
  		if (null != pluginList.get(activeProcessing))
  			pluginList.get(activeProcessing).onPause();
  		if (null != pluginList.get(activeExport))
  			pluginList.get(activeExport).onPause();
  	}
  
  	public void menuButtonPressed()
  	{
  		onShowPreferences();
  		Intent settingsActivity = new Intent(ApplicationScreen.getMainContext(), Preferences.class);
  		ApplicationScreen.instance.startActivity(settingsActivity);
  	}
  
  	// Called before preferences activity started
  	public void onShowPreferences()
  	{
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).onShowPreferences();
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onShowPreferences();
  		if (null != pluginList.get(activeProcessing))
  			pluginList.get(activeProcessing).onShowPreferences();
  		if (null != pluginList.get(activeExport))
  			pluginList.get(activeExport).onShowPreferences();
  	}
  
  	public void onGUICreate()
  	{
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).onGUICreate();
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onGUICreate();
  		if (null != pluginList.get(activeProcessing))
  			pluginList.get(activeProcessing).onGUICreate();
  		if (null != pluginList.get(activeExport))
  			pluginList.get(activeExport).onGUICreate();
  		isRestarting = true;
  	}
  
  	protected boolean	isUserClicked	= true;
  
  	public void onShutterClickNotUser()
  	{
  		isUserClicked = false;
  		onShutterClick();
  	}
  
  	public void onShutterClick()
  	{
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).onShutterClick();
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onShutterClick();
  		isUserClicked = true;
  	}
  
  	public void onFocusButtonClick()
  	{
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).onFocusButtonClick();
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onFocusButtonClick();
  	}
  
  	public boolean onTouch(View view, MotionEvent e)
  	{
  		int res = 0;
  		for (int i = 0; i < activeVF.size(); i++)
  			res += (pluginList.get(activeVF.get(i)).onTouch(view, e) ? 1 : 0);
  		if (null != pluginList.get(activeCapture))
  			res += (pluginList.get(activeCapture).onTouch(view, e) ? 1 : 0);
  		return (res > 0 ? true : false);
  	}
  
  	public boolean onMultiTouch(View view, MotionEvent e)
  	{
  		int res = 0;
  		for (int i = 0; i < activeVF.size(); i++)
  			res += (pluginList.get(activeVF.get(i)).onMultiTouch(view, e) ? 1 : 0);
  		if (null != pluginList.get(activeCapture))
  			res += (pluginList.get(activeCapture).onMultiTouch(view, e) ? 1 : 0);
  		return (res > 0 ? true : false);
  	}
  	
  	public Plugin getPlugin(String id)
  	{
  		return pluginList.get(id);
  	}
  
  	protected void AddModeSettings(String modeName, PreferenceFragment pf)
  	{
  		if (modeName.equals("super") && CameraController.isUseCamera2())
  		{
  			pf.addPreferencesFromResource(R.xml.preferences_processing_super);
  			return;
  		}
  
  		Mode mode = ConfigParser.getInstance().getMode(modeName);
  		if (mode == null)
  			return;
  		for (int j = 0; j < listCapture.size(); j++)
  		{
  			Plugin pg = listCapture.get(j);
  			if (mode.Capture.equals(pg.getID()))
  			{
  				addHeadersContent(pf, pg, false);
  			}
  		}
  		for (int j = 0; j < listProcessing.size(); j++)
  		{
  			// all modes below use simple processing and we should avoid
  			// duplicating DRO settings here.
  			if (modeName.equals("video") || modeName.equals("burstmode") || modeName.equals("expobracketing")
  					|| modeName.equals("burstmode"))
  				return;
  			Plugin pg = listProcessing.get(j);
  			if (mode.Processing.equals(pg.getID()))
  			{
  				addHeadersContent(pf, pg, false);
  			}
  		}
  	}
  
  	abstract public void loadHeaderContent(String settings, PreferenceFragment pf);
  
  	protected void addHeadersContent(PreferenceFragment pf, List<Plugin> list, boolean isAdvanced)
  	{
  		int size = list.size(), i = 0;
  		while (i < size)
  		{
  			addHeadersContent(pf, list.get(i), isAdvanced);
  			i++;
  		}
  	}
  
  	protected void addHeadersContent(PreferenceFragment pf, Plugin plugin, boolean isAdvanced)
  	{
  		if (null == plugin)
  			return;
  		if (!plugin.isShowPreferences)
  			return;
  		if (!isAdvanced)
  		{
  			if (plugin.getPreferenceName() == 0)
  				return;
  			pf.addPreferencesFromResource(plugin.getPreferenceName());
  			plugin.onPreferenceCreate(pf);
  		} else
  		{
  			if (plugin.getAdvancedPreferenceName() == 0)
  				return;
  			pf.addPreferencesFromResource(plugin.getAdvancedPreferenceName());
  		}
  		plugin.showInitialSummary(pf);
  	}
  
  	protected boolean isPreferenecesAvailable(List<Plugin> plugins, boolean isAdvanced)
  	{
  		boolean isAvailable = false;
  		for (int i = 0; i < plugins.size(); i++)
  		{
  			Plugin plugin = plugins.get(i);
  			if (plugin.isShowPreferences && !isAdvanced && plugin.getPreferenceName() != 0)
  				isAvailable = true;
  			else if (plugin.isShowPreferences && isAdvanced && plugin.getAdvancedPreferenceName() != 0)
  				isAvailable = true;
  		}
  		return isAvailable;
  	}
  
  	public void onOrientationChanged(int orientation)
  	{
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).onOrientationChanged(orientation);
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onOrientationChanged(orientation);
  		if (null != pluginList.get(activeProcessing))
  			pluginList.get(activeProcessing).onOrientationChanged(orientation);
  		if (null != pluginList.get(activeExport))
  			pluginList.get(activeExport).onOrientationChanged(orientation);
  	}
  
  	// can be situation when 2 plugins will handle this interface and it's not
  	// write.
  	// probably we should close it.
  	public boolean onKeyDown(boolean isFromMain, int keyCode, KeyEvent event)
  	{
  		int res = 0;
  		for (int i = 0; i < activeVF.size(); i++)
  			res += pluginList.get(activeVF.get(i)).onKeyDown(keyCode, event) ? 1 : 0;
  		if (null != pluginList.get(activeCapture))
  			res += pluginList.get(activeCapture).onKeyDown(keyCode, event) ? 1 : 0;
  		if (null != pluginList.get(activeProcessing))
  			res += pluginList.get(activeProcessing).onKeyDown(keyCode, event) ? 1 : 0;
  		if (null != pluginList.get(activeExport))
  			res += pluginList.get(activeExport).onKeyDown(keyCode, event) ? 1 : 0;
  		return (res > 0 ? true : false);
  	}
  
  	/******************************************************************************************************
  	 * VF/Capture Interfaces
  	 ******************************************************************************************************/
  
  	@Override
  	public void selectDefaults()
  	{
  		if (!isDefaultsSelected)
  			for (final Entry<String, Plugin> entry : this.pluginList.entrySet())
  			{
  				final Plugin plugin = entry.getValue();
  				plugin.onDefaultsSelect();
  			}
  		isDefaultsSelected = true;
  	}
  
  	@Override
  	public void selectImageDimension()
  	{
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).selectImageDimension();
  	}
  
  	public void setCameraPreviewSize()
  	{
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).setCameraPreviewSize();
  	}
  
  	@Override
  	public void onAutoFocus(boolean paramBoolean)
  	{
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).onAutoFocus(paramBoolean);
  
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onAutoFocus(paramBoolean);
  	}
  
  	void takePicture()
  	{
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).takePicture();
  	}
  
  	void onShutter()
  	{
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onShutter();
  	}
  
  	@Override
  	public void onImageTaken(int frame, byte[] frameData, int frame_len, int format)
  	{
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onImageTaken(frame, frameData, frame_len, format);
  	}
  
  	@TargetApi(21)
  	public void onCaptureCompleted(CaptureResult result)
  	{
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onCaptureCompleted(result);
  	}
  
  	@Override
  	public void addRequestID(int nFrame, int requestID)
  	{
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).addRequestID(nFrame, requestID);
  	}
  
  	@Override
  	public void collectExifData(byte[] frameData)
  	{
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).addToSharedMemExifTags(frameData);
  	}
  
  	@Override
  	public void onPreviewFrame(byte[] data)
  	{
  		// prevents plugin's views to disappear
  		if (isRestarting)
  		{
  			RelativeLayout pluginsLayout = (RelativeLayout) ApplicationScreen.instance.findViewById(R.id.mainLayout1);
  			pluginsLayout.requestLayout();
  			isRestarting = false;
  		}
  
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).onPreviewFrame(data);
  
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onPreviewFrame(data);
  	}
  
  	public void onFrameAvailable()
  	{
  		final Plugin plugin = pluginList.get(activeCapture);
  
  		if (plugin != null && plugin instanceof PluginCapture)
  		{
  			((PluginCapture) plugin).onFrameAvailable();
  		}
  	}
  
  	public void setupCameraParameters()
  	{
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).setupCameraParameters();
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).setupCameraParameters();
  	}
  
  	public void onCameraParametersSetup()
  	{
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).onCameraParametersSetup();
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onCameraParametersSetup();
  	}
  
  	public void onCameraSetup()
  	{
  		for (int i = 0; i < activeVF.size(); i++)
  			pluginList.get(activeVF.get(i)).onCameraSetup();
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onCameraSetup();
  	}
  
  	/******************************************************************************************************
  	 * Processing Interfaces
  	 ******************************************************************************************************/
  
  	public int getResultYUV(int index)
  	{
  		if (null != pluginList.get(activeProcessing))
  			return pluginList.get(activeProcessing).getResultYUV(index);
  		else
  			return -1;
  	}
  
  	/******************************************************************************************************
  	 * Filter Interfaces
  	 ******************************************************************************************************/
  
  	/******************************************************************************************************
  	 * Export Interfaces
  	 ******************************************************************************************************/
  	// broadcast message handler
  	// handler will notify all active plugins where data is located and length
  	// of data.
  	// First idea - is data array and data length in main. Can be accessed by
  	// any interested plugin.
  	// message can pass 2 int parameters. can be used as message id or short
  	// data pass
  	void onBroadcast(int arg1, int arg2)
  	{
  		boolean res;
  		for (int i = 0; i < activeVF.size(); i++)
  		{
  			res = pluginList.get(activeVF.get(i)).onBroadcast(arg1, arg2);
  			if (res)
  				return;
  		}
  		if (null != pluginList.get(activeCapture))
  		{
  			res = pluginList.get(activeCapture).onBroadcast(arg1, arg2);
  			if (res)
  				return;
  		}
  		if (null != pluginList.get(activeProcessing))
  		{
  			res = pluginList.get(activeProcessing).onBroadcast(arg1, arg2);
  			if (res)
  				return;
  		}
  		if (null != pluginList.get(activeExport))
  		{
  			res = pluginList.get(activeExport).onBroadcast(arg1, arg2);
  			if (res)
  				return;
  		}
  	}
  
  	/******************************************************************************************************
  	 * Message handler
  	 ******************************************************************************************************/
  	public boolean handleMessage(Message msg)
  	{
  		return handleApplicationMessage(msg);
  	}
  
  	public boolean handleApplicationMessage(Message msg)
  	{
  		long sessionID = 0;
  		
  		switch (msg.what)
  		{
  		case ApplicationInterface.MSG_NO_CAMERA:
  			break;
  
  		case ApplicationInterface.MSG_CAPTURE_FINISHED:
  
  			/*
  			 * Debug code for Galaxy S6 in Super mode. Look at Camera2 for more
  			 * details
  			 */
  			// CameraController.onCaptureFinished();
  
  			if (ApplicationScreen.instance.getFlashModePref(ApplicationScreen.sDefaultFlashValue) == CameraParameters.FLASH_MODE_CAPTURE_TORCH)
  			{
  				// If flashMode == FLASH_MODE_CAPTURE_TORCH, then turn off torch
  				// after capturing completed.
  				CameraController.setCameraFlashMode(CameraParameters.FLASH_MODE_OFF);
  			}
  
  			for (int i = 0; i < activeVF.size(); i++)
  				pluginList.get(activeVF.get(i)).onCaptureFinished();
  
  			ApplicationScreen.getGUIManager().onCaptureFinished();
  			ApplicationScreen.getGUIManager().startProcessingAnimation();
  
  			int id = ApplicationScreen.getAppResources().getIdentifier(getActiveMode().modeName, "string",
  					ApplicationScreen.instance.getPackageName());
  			String modeName = ApplicationScreen.getAppResources().getString(id);
  			addToSharedMem("mode_name" + (String) msg.obj, modeName);
  			// start async task for further processing
  			cntProcessing++;
  
  			sessionID = Long.valueOf((String) msg.obj);
  			
  			// Map sessionID and processing plugin, because active plugin may be
  			// changed before image processing will start.
  			// We don't map export plugin, because it's the same for all modes.
  			processingPluginList.put(sessionID, pluginList.get(activeProcessing));
  
  			Intent mServiceIntent = new Intent(ApplicationScreen.instance, ProcessingService.class);
  
  			// Pass to Service sessionID and some other parameters, that may be
  			// required.
  			mServiceIntent.putExtra("sessionID", sessionID);
  			CameraController.Size imageSize = CameraController.getCameraImageSize();
  			if (CameraController.motozChangeResolution)//isMotoZ && !CameraController.getIsCamera2())
  			{
  				int w = imageSize.getWidth()==1944?1936:imageSize.getWidth();
  				int h = imageSize.getHeight()==1944?1936:imageSize.getHeight();
  				PluginManager.getInstance().addToSharedMem("imageWidth" + sessionID, String.valueOf(w));
  				PluginManager.getInstance().addToSharedMem("imageHeight" + sessionID, String.valueOf(h));
  			}
  			else
  			{
  				PluginManager.getInstance().addToSharedMem("imageWidth" + sessionID, String.valueOf(imageSize.getWidth()));
  				PluginManager.getInstance().addToSharedMem("imageHeight" + sessionID, String.valueOf(imageSize.getHeight()));
  			}
  			PluginManager.getInstance().addToSharedMem("wantLandscapePhoto" + sessionID, String.valueOf(ApplicationScreen.getWantLandscapePhoto()));
  			PluginManager.getInstance().addToSharedMem("cameraMirrored" + sessionID, String.valueOf(CameraController.isFrontCamera()));
  
  			// Start processing service with current sessionID.
  			ApplicationScreen.instance.startService(mServiceIntent);
  
  			ApplicationScreen.instance.muteShutter(false);
  
  			break;
  
  		case ApplicationInterface.MSG_CAPTURE_FINISHED_NORESULT:
  
  			/*
  			 * Debug code for Galaxy S6 in Super mode. Look at Camera2 for more
  			 * details
  			 */
  			// CameraController.onCaptureFinished();
  
  			if (ApplicationScreen.instance.getFlashModePref(ApplicationScreen.sDefaultFlashValue) == CameraParameters.FLASH_MODE_CAPTURE_TORCH)
  			{
  				// If flashMode == FLASH_MODE_CAPTURE_TORCH, then turn off torch
  				// after capturing completed.
  				CameraController.setCameraFlashMode(CameraParameters.FLASH_MODE_OFF);
  			}
  
  			for (int i = 0; i < activeVF.size(); i++)
  				pluginList.get(activeVF.get(i)).onCaptureFinished();
  
  			ApplicationScreen.getGUIManager().onCaptureFinished();
  			ApplicationScreen.getGUIManager().startProcessingAnimation();
  
  			ApplicationScreen.instance.muteShutter(false);
  
  			ApplicationScreen.getGUIManager().lockControls = false;
  
  			sendMessage(ApplicationInterface.MSG_BROADCAST, ApplicationInterface.MSG_CONTROL_UNLOCKED);
  
  			ApplicationScreen.getGUIManager().onExportFinished();
  
  			for (int i = 0; i < activeVF.size(); i++)
  				pluginList.get(activeVF.get(i)).onExportFinished();
  
  			break;
  
  		case ApplicationInterface.MSG_START_POSTPROCESSING:
  			if (null != pluginList.get(activeProcessing))
  			{
  				ApplicationScreen.getGUIManager().lockControls = true;
  				sendMessage(ApplicationInterface.MSG_BROADCAST, ApplicationInterface.MSG_CONTROL_LOCKED);
  
  				pluginList.get(activeProcessing).onStartPostProcessing();
  				ApplicationScreen.getGUIManager().onPostProcessingStarted();
  			}
  			break;
  
  		case ApplicationInterface.MSG_POSTPROCESSING_FINISHED:
  			sessionID = 0;
  			String sSessionID = getFromSharedMem("sessionID");
  			if (sSessionID != null)
  				sessionID = Long.parseLong(getFromSharedMem("sessionID"));
  
  			// notify GUI about saved images
  			ApplicationScreen.getGUIManager().lockControls = false;
  			sendMessage(ApplicationInterface.MSG_BROADCAST, ApplicationInterface.MSG_CONTROL_UNLOCKED);
  
  			ApplicationScreen.getGUIManager().onPostProcessingFinished();
  			if (null != pluginList.get(activeExport) && 0 != sessionID) 
  			{
  				pluginList.get(activeExport).onExportActive(sessionID);
  			}
  			else
  			{
  				ApplicationScreen.getMessageHandler().sendEmptyMessage(ApplicationInterface.MSG_EXPORT_FINISHED);
  				clearSharedMemory(sessionID);
  			}
  
  			break;
  		case ApplicationInterface.MSG_EXPORT_FINISHED:
  			// event from plugin that saving finished and memory can be freed
  			if (cntProcessing > 0)
  				cntProcessing--;
  			// free memory in processing
  			if (null != pluginList.get(activeProcessing))
  				pluginList.get(activeProcessing).freeMemory();
  
  			// notify GUI about saved images
  			ApplicationScreen.getGUIManager().onExportFinished();
  
  			// notify capture plugins that saving finished
  			if (null != pluginList.get(activeCapture))
  				pluginList.get(activeCapture).onExportFinished();
  			for (int i = 0; i < activeVF.size(); i++)
  				pluginList.get(activeVF.get(i)).onExportFinished();
  
  			if (ApplicationScreen.instance.getIntent().getAction() != null)
  			{
  				if (ApplicationScreen.instance.getIntent().getAction().equals(MediaStore.ACTION_IMAGE_CAPTURE))
  				{
  					ApplicationScreen.getMessageHandler().sendEmptyMessage(ApplicationInterface.MSG_RETURN_CAPTURED);
  				}
  			}
  
  			ApplicationScreen.getGUIManager().lockControls = false;
  			sendMessage(ApplicationInterface.MSG_BROADCAST, ApplicationInterface.MSG_CONTROL_UNLOCKED);
  
  			break;
  
  		case ApplicationInterface.MSG_EXPORT_FINISHED_IOEXCEPTION:
  			// event from plugin that saving finished and memory can be freed
  			if (cntProcessing > 0)
  				cntProcessing--;
  			// free memory in processing
  			if (null != pluginList.get(activeProcessing))
  				pluginList.get(activeProcessing).freeMemory();
  
  			// notify GUI about saved images
  			ApplicationScreen.getGUIManager().onExportFinished();
  
  			// notify capture plugins that saving finished
  			if (null != pluginList.get(activeCapture))
  				pluginList.get(activeCapture).onExportFinished();
  			for (int i = 0; i < activeVF.size(); i++)
  				pluginList.get(activeVF.get(i)).onExportFinished();
  
  			Toast.makeText(ApplicationScreen.getMainContext(), "Can't save data - no free space left or problems while saving occurred.",
  					Toast.LENGTH_LONG).show();
  
  			ApplicationScreen.getGUIManager().lockControls = false;
  			sendMessage(ApplicationInterface.MSG_BROADCAST, ApplicationInterface.MSG_CONTROL_UNLOCKED);
  
  			break;
  
  		case ApplicationInterface.MSG_DELAYED_CAPTURE:
  			for (int i = 0; i < activeVF.size(); i++)
  				pluginList.get(activeVF.get(i)).onShutterClick();
  			if (null != pluginList.get(activeCapture))
  				pluginList.get(activeCapture).onShutterClick();
  			break;
  
  		case ApplicationInterface.MSG_RETURN_CAPTURED:
  			ApplicationScreen.instance.setResult(Activity.RESULT_OK);
  			ApplicationScreen.instance.finish();
  			break;
  
  		case ApplicationInterface.MSG_OPENGL_LAYER_SHOW:
  			ApplicationScreen.instance.showOpenGLLayer(1);
  			break;
  
  		case ApplicationInterface.MSG_OPENGL_LAYER_SHOW_V2:
  			ApplicationScreen.instance.showOpenGLLayer(2);
  			break;
  
  		case ApplicationInterface.MSG_OPENGL_LAYER_HIDE:
  			ApplicationScreen.instance.hideOpenGLLayer();
  			break;
  
  		case ApplicationInterface.MSG_OPENGL_LAYER_RENDERMODE_CONTINIOUS:
  			ApplicationScreen.instance.glSetRenderingMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
  			break;
  
  		case ApplicationInterface.MSG_OPENGL_LAYER_RENDERMODE_WHEN_DIRTY:
  			ApplicationScreen.instance.glSetRenderingMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
  			break;
  
  		case ApplicationInterface.MSG_PROCESSING_BLOCK_UI:
  			ApplicationScreen.getGUIManager().processingBlockUI();
  			break;
  
  		case ApplicationInterface.MSG_BROADCAST:
  			onBroadcast(msg.arg1, msg.arg2);
  			break;
  		default:
  			break;
  		}
  
  		return true;
  	}
  
  	/******************************************************************************************************
  	 * Work with hash table
  	 ******************************************************************************************************/
  	public boolean addToSharedMem(String key, String value)
  	{
  		sharedMemory.put(key, value);
  
  		return true;
  	}
  
  	@TargetApi(21)
  	public boolean addRAWCaptureResultToSharedMem(String key, CaptureResult value)
  	{
  		rawCaptureResults.put(key, value);
  
  		return true;
  	}
  
  	public boolean addToSharedMemExifTagsFromJPEG(final byte[] paramArrayOfByte, final long SessionID, final int num)
  	{
  		try
  		{
  			InputStream is = new ByteArrayInputStream(paramArrayOfByte);
  			Metadata metadata = JpegMetadataReader.readMetadata(is);
  			Directory exifDirectory = metadata.getDirectory(ExifSubIFDDirectory.class);
  			String s1 = exifDirectory.getString(ExifSubIFDDirectory.TAG_EXPOSURE_TIME);
  			String s2 = exifDirectory.getString(ExifSubIFDDirectory.TAG_FNUMBER);
  			String s3 = exifDirectory.getString(ExifSubIFDDirectory.TAG_FLASH);
  			String s4 = exifDirectory.getString(ExifSubIFDDirectory.TAG_FOCAL_LENGTH);
  			String s5 = exifDirectory.getString(ExifSubIFDDirectory.TAG_ISO_EQUIVALENT);
  			String s6 = exifDirectory.getString(ExifSubIFDDirectory.TAG_WHITE_BALANCE_MODE);
  			String s9 = exifDirectory.getString(ExifSubIFDDirectory.TAG_SPECTRAL_SENSITIVITY);
  			String s10 = exifDirectory.getString(ExifSubIFDDirectory.TAG_EXIF_VERSION);
  			String s11 = exifDirectory.getString(ExifSubIFDDirectory.TAG_SCENE_CAPTURE_TYPE);
  			String s12 = exifDirectory.getString(ExifSubIFDDirectory.TAG_METERING_MODE);
  
  			Directory exif2Directory = metadata.getDirectory(ExifIFD0Directory.class);
  			String s7 = exif2Directory.getString(ExifIFD0Directory.TAG_MAKE);
  			String s8 = exif2Directory.getString(ExifIFD0Directory.TAG_MODEL);
  
  			if (num != -1 && s1 != null)
  				addToSharedMem("exiftag_exposure_time" + num + SessionID, s1);
  			else if (s1 != null)
  				addToSharedMem("exiftag_exposure_time" + SessionID, s1);
  			if (s2 != null)
  				addToSharedMem("exiftag_aperture" + SessionID, s2);
  			if (s3 != null)
  				addToSharedMem("exiftag_flash" + SessionID, s3);
  			if (s4 != null)
  				addToSharedMem("exiftag_focal_lenght" + SessionID, s4);
  			if (s5 != null)
  				addToSharedMem("exiftag_iso" + SessionID, s5);
  			if (s6 != null)
  				addToSharedMem("exiftag_white_balance" + SessionID, s6);
  			if (s7 != null)
  				addToSharedMem("exiftag_make" + SessionID, s7);
  			if (s8 != null)
  				addToSharedMem("exiftag_model" + SessionID, s8);
  			if (s9 != null)
  				addToSharedMem("exiftag_spectral_sensitivity" + SessionID, s9);
  			if (s10 != null)
  				addToSharedMem("exiftag_version" + SessionID, s10);
  			if (s11 != null)
  				addToSharedMem("exiftag_scene_capture_type" + SessionID, s11);
  			if (s12 != null)
  				addToSharedMem("exiftag_metering_mode" + SessionID, s12);
  
  		} catch (JpegProcessingException e1)
  		{
  			e1.printStackTrace();
  			return false;
  		}
  		return true;
  	}
  
  	@TargetApi(21)
  	public boolean addToSharedMemExifTagsFromCaptureResult(final CaptureResult result, final long SessionID,
  			final int num)
  	{
  		String exposure_time = String.valueOf(result.get(CaptureResult.SENSOR_EXPOSURE_TIME));
  		String sensitivity = String.valueOf(result.get(CaptureResult.SENSOR_SENSITIVITY));
  		String aperture = String.valueOf(result.get(CaptureResult.LENS_APERTURE));
  		String focal_lenght = String.valueOf(result.get(CaptureResult.LENS_FOCAL_LENGTH));
  		String flash_mode = String.valueOf(result.get(CaptureResult.FLASH_MODE));
  		String awb_mode = String.valueOf(result.get(CaptureResult.CONTROL_AWB_MODE));
  
  		if (num != -1 && exposure_time != null && !exposure_time.equals("null"))
  			addToSharedMem("exiftag_exposure_time" + num + SessionID, exposure_time);
  		else if (exposure_time != null && !exposure_time.equals("null"))
  			addToSharedMem("exiftag_exposure_time" + SessionID, exposure_time);
  		if (sensitivity != null && !sensitivity.equals("null"))
  			addToSharedMem("exiftag_iso" + SessionID, sensitivity);
  		if (aperture != null && !aperture.equals("null"))
  			addToSharedMem("exiftag_aperture" + SessionID, aperture);
  		if (focal_lenght != null && !focal_lenght.equals("null"))
  			addToSharedMem("exiftag_focal_lenght" + SessionID, focal_lenght);
  		if (flash_mode != null && !flash_mode.equals("null"))
  			addToSharedMem("exiftag_flash" + SessionID, flash_mode);
  		if (awb_mode != null && !awb_mode.equals("null"))
  			addToSharedMem("exiftag_white_balance" + SessionID, awb_mode);
  
  		return true;
  	}
  
  	public boolean addToSharedMemExifTagsFromCamera(final long SessionID)
  	{
  		Camera.Parameters params = CameraController.getCameraParameters();
  		if (params == null)
  			return false;
  
  		String s1 = null;
  		if (params.getSupportedWhiteBalance() != null)
  			s1 = params.getWhiteBalance().compareTo(
  					ApplicationScreen.getAppResources().getString(R.string.wbAutoSystem)) == 0 ? String.valueOf(0)
  					: String.valueOf(1);
  		String s2 = Build.MANUFACTURER;
  		String s3 = Build.MODEL;
  
  		String s4 = null;
  		if (ApplicationScreen.getGUIManager().mISOSupported)
  			s4 = String.valueOf(CameraController.getISOMode());
  
  		if (s1 != null)
  			addToSharedMem("exiftag_white_balance" + SessionID, s1);
  		if (s2 != null)
  			addToSharedMem("exiftag_make" + SessionID, s2);
  		if (s3 != null)
  			addToSharedMem("exiftag_model" + SessionID, s3);
  		if (s4 != null && (s4.compareTo("auto") != 0))
  			addToSharedMem("exiftag_iso" + SessionID, s4);
  		return true;
  	}
  
  	public String getFromSharedMem(String key)
  	{
  		return sharedMemory.get(key);
  	}
  
  	public boolean containsSharedMem(String key)
  	{
  		if (!sharedMemory.containsKey(key))
  			return false;
  		return true;
  	}
  
  	@TargetApi(21)
  	public CaptureResult getFromRAWCaptureResults(String key)
  	{
  		return rawCaptureResults.get(key);
  	}
  
  	@TargetApi(21)
  	private boolean containsRAWCaptureResults(String key)
  	{
  		if (rawCaptureResults.containsKey(key))
  			return true;
  		else
  			return false;
  	}
  
  	public void clearSharedMemory(long sessionID)
  	{
  		String partKey = String.valueOf(sessionID);
  		Enumeration<String> e = sharedMemory.keys();
  		while (e.hasMoreElements())
  		{
  			String i = (String) e.nextElement();
  			if (i.contains(partKey))
  				sharedMemory.remove(i);
  		}
  
  		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
  			clearRAWCaptureResults(sessionID);
  	}
  
  	@TargetApi(21)
  	public void clearRAWCaptureResults(long sessionID)
  	{
  		String partKey = String.valueOf(sessionID);
  		Enumeration<String> e2 = rawCaptureResults.keys();
  		while (e2.hasMoreElements())
  		{
  			String i = (String) e2.nextElement();
  			if (i.contains(partKey))
  				rawCaptureResults.remove(i);
  		}
  	}
  
  	public int sizeOfSharedMemory()
  	{
  		return sharedMemory.size();
  	}
  
  	public void removeFromSharedMemory(String key)
  	{
  		Enumeration<String> e = sharedMemory.keys();
  		while (e.hasMoreElements())
  		{
  			String i = (String) e.nextElement();
  			if (i.equals(key))
  				sharedMemory.remove(i);
  		}
  	}
  
  	public boolean muteSounds()
  	{
  		final Plugin plugin = pluginList.get(activeCapture);
  		if (plugin != null && plugin instanceof PluginCapture)
  		{
  			return ((PluginCapture) plugin).muteSound();
  		} else
  		{
  			return false;
  		}
  	}
  
  	/******************************************************************************************************
  	 * OpenGL layer functions
  	 ******************************************************************************************************/
  	@Override
  	public boolean shouldPreviewToGPU()
  	{
  		final Plugin plugin = pluginList.get(activeCapture);
  
  		if (plugin != null && (plugin instanceof PluginCapture))
  		{
  			return ((PluginCapture) plugin).shouldPreviewToGPU();
  		} else
  		{
  			return false;
  		}
  	}
  
  	public boolean isGLSurfaceNeeded()
  	{
  		boolean ret = false;
  		if (null != pluginList.get(activeCapture))
  		{
  			if (pluginList.get(activeCapture).isGLSurfaceNeeded())
  				ret = true;
  		}
  		return ret;
  	}
  
  	public void onGLSurfaceCreated(GL10 gl, EGLConfig config)
  	{
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onGLSurfaceCreated(gl, config);
  	}
  
  	public void onGLSurfaceChanged(GL10 gl, int width, int height)
  	{
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onGLSurfaceChanged(gl, width, height);
  	}
  
  	public void onGLDrawFrame(GL10 gl)
  	{
  		if (null != pluginList.get(activeCapture))
  			pluginList.get(activeCapture).onGLDrawFrame(gl);
  	}
  
  	// /////////////////////////////////////
  	// Utils
  	// /////////////////////////////////////
  
  	public int getProcessingCounter()
  	{
  		return cntProcessing;
  	}
  
  	public String getFileFormat()
  	{
  		if (CameraController.isUseCamera2())
  		{
  			return SavingService.getExportFileName(getActiveMode().modeSaveNameHAL);
  		} else
  		{
  			return SavingService.getExportFileName(getActiveMode().modeSaveName);
  		}
  	}
  
  	public static File getSaveDir(boolean forceSaveToInternalMemory) {
  		return SavingService.getSaveDir(forceSaveToInternalMemory);
  	}
  	
  	public static DocumentFile getSaveDirNew(boolean forceSaveToInternalMemory) {
  		return SavingService.getSaveDirNew(forceSaveToInternalMemory);
  	}
  	
  	public static String getExportFileName(String modeName) {
  		return SavingService.getExportFileName(modeName);
  	}
  	
  	// Save result pictures method.
  	public void saveResultPicture(long sessionID)
  	{
  		Intent mServiceIntent = new Intent(ApplicationScreen.instance, SavingService.class);
  
  		// Pass to Service sessionID.
  		mServiceIntent.putExtra("sessionID", sessionID);
  
  		// Start processing service with current sessionID.
  		ApplicationScreen.instance.startService(mServiceIntent);
  	}
  
  	public void saveInputFile(boolean isYUV, Long SessionID, int i, byte[] buffer, int yuvBuffer, String fileFormat)
  	{
  		// if Android 5+ use new saving method.
  		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
  		{
  			saveInputFileNew(isYUV, SessionID, i, buffer, yuvBuffer, fileFormat);
  			return;
  		}
  
  		CameraController.Size imageSize = CameraController.getCameraImageSize();
  		ContentValues values = null;
  		String resultOrientation = getFromSharedMem("frameorientation" + (i + 1) + Long.toString(SessionID));
  		if (resultOrientation == null)
  		{
  			resultOrientation = getFromSharedMem("frameorientation" + i + Long.toString(SessionID));
  		}
  
  		String resultMirrored = getFromSharedMem("framemirrored" + (i + 1) + Long.toString(SessionID));
  		if (resultMirrored == null)
  		{
  			resultMirrored = getFromSharedMem("framemirrored" + i + Long.toString(SessionID));
  		}
  		Boolean cameraMirrored = false;
  		if (resultMirrored != null)
  			cameraMirrored = Boolean.parseBoolean(resultMirrored);
  
  		int mDisplayOrientation = 0;
  		if (resultOrientation != null)
  		{
  			mDisplayOrientation = Integer.parseInt(resultOrientation);
  		}
  
  		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ApplicationScreen.getMainContext());
  		boolean saveGeoInfo = prefs.getBoolean("useGeoTaggingPrefExport", false);
  
  		File saveDir = getSaveDir(false);
  		File file = new File(saveDir, fileFormat + ".jpg");
  		FileOutputStream os = null;
  
  		try
  		{
  			try
  			{
  				os = new FileOutputStream(file);
  			} catch (Exception e)
  			{
  				// save always if not working saving to sdcard
  				e.printStackTrace();
  				saveDir = getSaveDir(true);
  				file = new File(saveDir, fileFormat + ".jpg");
  
  				os = new FileOutputStream(file);
  			}
  		} catch (FileNotFoundException e1)
  		{
  			// TODO Auto-generated catch block
  			e1.printStackTrace();
  		}
  
  		try
  		{
  			if (os != null)
  			{
  				if (!isYUV)
  				{
  					os.write(buffer);
  				} else
  				{
  					jpegQuality = Integer.parseInt(prefs.getString(MainScreen.sJPEGQualityPref, "95"));
  
  					com.almalence.YuvImage image = new com.almalence.YuvImage(yuvBuffer, ImageFormat.NV21,
  							imageSize.getWidth(), imageSize.getHeight(), null);
  					// to avoid problems with SKIA
  					int cropHeight = image.getHeight() - image.getHeight() % 16;
  					image.compressToJpeg(new Rect(0, 0, image.getWidth(), cropHeight), jpegQuality, os);
  
  					mDisplayOrientation = saveExifToInput(file, mDisplayOrientation, cameraMirrored, saveGeoInfo);
  				}
  				os.close();
  			}
  		} catch (Exception e)
  		{
  			e.printStackTrace();
  		}
  
  		values = new ContentValues();
  		values.put(ImageColumns.TITLE, file.getName().substring(0, file.getName().lastIndexOf(".")));
  		values.put(ImageColumns.DISPLAY_NAME, file.getName());
  		values.put(ImageColumns.DATE_TAKEN, System.currentTimeMillis());
  		values.put(ImageColumns.MIME_TYPE, "image/jpeg");
  		values.put(ImageColumns.ORIENTATION, mDisplayOrientation);
  		values.put(ImageColumns.DATA, file.getAbsolutePath());
  
  		if (saveGeoInfo)
  		{
  			Location l = MLocation.getLocation(ApplicationScreen.getMainContext());
  
  			if (l != null)
  			{
  				values.put(ImageColumns.LATITUDE, l.getLatitude());
  				values.put(ImageColumns.LONGITUDE, l.getLongitude());
  			}
  		}
  
  		ApplicationScreen.instance.getContentResolver().insert(Images.Media.EXTERNAL_CONTENT_URI, values);
  	}
  
  	private void saveInputFileNew(boolean isYUV, Long SessionID, int i, byte[] buffer, int yuvBuffer, String fileFormat)
  	{
  		
  		int mImageWidth = Integer.parseInt(PluginManager.getInstance().getFromSharedMem("imageWidth" + SessionID));
  		int mImageHeight = Integer.parseInt(PluginManager.getInstance().getFromSharedMem("imageHeight" + SessionID));
  		ContentValues values = null;
  		String resultOrientation = getFromSharedMem("frameorientation" + (i + 1) + Long.toString(SessionID));
  		if (resultOrientation == null)
  		{
  			resultOrientation = getFromSharedMem("frameorientation" + i + Long.toString(SessionID));
  		}
  
  		String resultMirrored = getFromSharedMem("framemirrored" + (i + 1) + Long.toString(SessionID));
  		if (resultMirrored == null)
  		{
  			resultMirrored = getFromSharedMem("framemirrored" + i + Long.toString(SessionID));
  		}
  
  		Boolean cameraMirrored = false;
  		if (resultMirrored != null)
  			cameraMirrored = Boolean.parseBoolean(resultMirrored);
  
  		int mDisplayOrientation = 0;
  		if (resultOrientation != null)
  		{
  			mDisplayOrientation = Integer.parseInt(resultOrientation);
  		}
  
  		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ApplicationScreen.getMainContext());
  		boolean saveGeoInfo = prefs.getBoolean("useGeoTaggingPrefExport", false);
  
  		DocumentFile file;
  		DocumentFile saveDir = getSaveDirNew(false);
  		if (saveDir == null || !saveDir.exists())
  		{
  			return;
  		}
  
  		file = saveDir.createFile("image/jpeg", fileFormat);
  		if (file == null || !file.canWrite())
  		{
  			return;
  		}
  
  		OutputStream os = null;
  		File bufFile = new File(ApplicationScreen.instance.getFilesDir(), "buffer.jpeg");
  		try
  		{
  			os = new FileOutputStream(bufFile);
  		} catch (Exception e)
  		{
  			e.printStackTrace();
  		}
  
  		if (os != null)
  		{
  			try
  			{
  				if (!isYUV)
  				{
  					os.write(buffer);
  				} else
  				{
  					jpegQuality = Integer.parseInt(prefs.getString(MainScreen.sJPEGQualityPref, "95"));
  
  					com.almalence.YuvImage image = new com.almalence.YuvImage(yuvBuffer, ImageFormat.NV21,
  							mImageWidth, mImageHeight, null);
  					// to avoid problems with SKIA
  					int cropHeight = image.getHeight() - image.getHeight() % 16;
  					image.compressToJpeg(new Rect(0, 0, image.getWidth(), cropHeight), jpegQuality, os);
  				}
  				os.close();
  			} catch (IOException e)
  			{
  				// TODO Auto-generated catch block
  				e.printStackTrace();
  			}
  
  			mDisplayOrientation = saveExifToInput(bufFile, mDisplayOrientation, cameraMirrored, saveGeoInfo);
  
  			// Copy buffer image with exif tags into result file.
  			InputStream is = null;
  			int len;
  			byte[] buf = new byte[1024];
  			try
  			{
  				os = ApplicationScreen.instance.getContentResolver().openOutputStream(file.getUri());
  				is = new FileInputStream(bufFile);
  				while ((len = is.read(buf)) > 0)
  				{
  					os.write(buf, 0, len);
  				}
  				is.close();
  				os.close();
  			} catch (Exception e)
  			{
  				e.printStackTrace();
  			}
  		}
  
  		bufFile.delete();
  
  		values = new ContentValues();
  		values.put(ImageColumns.TITLE, file.getName().substring(0, file.getName().lastIndexOf(".")));
  		values.put(ImageColumns.DISPLAY_NAME, file.getName());
  		values.put(ImageColumns.DATE_TAKEN, System.currentTimeMillis());
  		values.put(ImageColumns.MIME_TYPE, "image/jpeg");
  		values.put(ImageColumns.ORIENTATION, mDisplayOrientation);
  		
  		String filePath = file.getName();
  		// If we able to get File object, than get path from it.
  		// fileObject should not be null for files on phone memory.
  		File fileObject = Util.getFileFromDocumentFile(file);
  		if (fileObject != null)
  		{
  			filePath = fileObject.getAbsolutePath();
  			values.put(ImageColumns.DATA, filePath);
  		} else
  		{
  			// This case should typically happen for files saved to SD
  			// card.
  			String documentPath = Util.getAbsolutePathFromDocumentFile(file);
  			values.put(ImageColumns.DATA, documentPath);
  		}
  
  		if (saveGeoInfo)
  		{
  			Location l = MLocation.getLocation(ApplicationScreen.getMainContext());
  			if (l != null)
  			{
  				values.put(ImageColumns.LATITUDE, l.getLatitude());
  				values.put(ImageColumns.LONGITUDE, l.getLongitude());
  			}
  		}
  
  		ApplicationScreen.instance.getContentResolver().insert(Images.Media.EXTERNAL_CONTENT_URI, values);
  	}
  
  	public static int saveExifToInput(File file, int displayOrientation, boolean cameraMirrored, boolean saveGeo)
  	{
  		try
  		{
  			ExifInterface ei = new ExifInterface(file.getAbsolutePath());
  			int exif_orientation = ExifInterface.ORIENTATION_NORMAL;
  			switch (displayOrientation)
  			{
  			default:
  			case 0:
  				exif_orientation = ExifInterface.ORIENTATION_NORMAL;
  				break;
  			case 90:
  				if (cameraMirrored)
  				{
  					displayOrientation = 270;
  					exif_orientation = ExifInterface.ORIENTATION_ROTATE_270;
  				} else
  				{
  					exif_orientation = ExifInterface.ORIENTATION_ROTATE_90;
  				}
  				break;
  			case 180:
  				exif_orientation = ExifInterface.ORIENTATION_ROTATE_180;
  				break;
  			case 270:
  				if (cameraMirrored)
  				{
  					displayOrientation = 90;
  					exif_orientation = ExifInterface.ORIENTATION_ROTATE_90;
  				} else
  				{
  					exif_orientation = ExifInterface.ORIENTATION_ROTATE_270;
  				}
  				break;
  			}
  			ei.setAttribute(ExifInterface.TAG_ORIENTATION, "" + exif_orientation);
  
  			if (saveGeo)
  			{
  				Location l = MLocation.getLocation(ApplicationScreen.getMainContext());
  
  				if (l != null)
  				{
  					ei.setAttribute(ExifInterface.TAG_GPS_LATITUDE, GPSTagsConverter.convert(l.getLatitude()));
  					ei.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, GPSTagsConverter.latitudeRef(l.getLatitude()));
  					ei.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, GPSTagsConverter.convert(l.getLongitude()));
  					ei.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF,
  							GPSTagsConverter.longitudeRef(l.getLongitude()));
  
  				}
  			}
  
  			ei.saveAttributes();
  		} catch (IOException e)
  		{
  			// TODO Auto-generated catch block
  			e.printStackTrace();
  		}
  
  		return displayOrientation;
  	}
  
  	public void sendMessage(int what, String obj, int arg1, int arg2)
  	{
  		Message message = new Message();
  		message.obj = String.valueOf(obj);
  		message.arg1 = arg1;
  		message.arg2 = arg2;
  		message.what = what;
  		ApplicationScreen.getMessageHandler().sendMessage(message);
  	}
  
  	public void sendMessage(int what, int arg1)
  	{
  		Message message = new Message();
  		message.arg1 = arg1;
  		message.what = what;
  		ApplicationScreen.getMessageHandler().sendMessage(message);
  	}
  
  	public void sendMessage(int what, String obj)
  	{
  		Message message = new Message();
  		message.obj = String.valueOf(obj);
  		message.what = what;
  		ApplicationScreen.getMessageHandler().sendMessage(message);
  	}
  }
  