All Downloads are FREE. Search and download functionalities are using the official Maven repository.

soot.jimple.infoflow.android.SetupApplication Maven / Gradle / Ivy

There is a newer version: 2.14.1
Show newest version
/*******************************************************************************
 * Copyright (c) 2012 Secure Software Engineering Group at EC SPRIDE.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v2.1
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Contributors: Christian Fritz, Steven Arzt, Siegfried Rasthofer, Eric
 * Bodden, and others.
 ******************************************************************************/
package soot.jimple.infoflow.android;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.stream.XMLStreamException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
import org.xmlpull.v1.XmlPullParserException;

import heros.solver.Pair;
import soot.G;
import soot.Main;
import soot.PackManager;
import soot.Scene;
import soot.SootClass;
import soot.SootField;
import soot.SootMethod;
import soot.Unit;
import soot.jimple.Stmt;
import soot.jimple.infoflow.AbstractInfoflow;
import soot.jimple.infoflow.BackwardsInfoflow;
import soot.jimple.infoflow.IInfoflow;
import soot.jimple.infoflow.Infoflow;
import soot.jimple.infoflow.InfoflowConfiguration;
import soot.jimple.infoflow.InfoflowConfiguration.SootIntegrationMode;
import soot.jimple.infoflow.android.InfoflowAndroidConfiguration.CallbackConfiguration;
import soot.jimple.infoflow.android.InfoflowAndroidConfiguration.IccConfiguration;
import soot.jimple.infoflow.android.callbacks.AbstractCallbackAnalyzer;
import soot.jimple.infoflow.android.callbacks.AndroidCallbackDefinition;
import soot.jimple.infoflow.android.callbacks.AndroidCallbackDefinition.CallbackType;
import soot.jimple.infoflow.android.callbacks.DefaultCallbackAnalyzer;
import soot.jimple.infoflow.android.callbacks.FastCallbackAnalyzer;
import soot.jimple.infoflow.android.callbacks.filters.AlienFragmentFilter;
import soot.jimple.infoflow.android.callbacks.filters.AlienHostComponentFilter;
import soot.jimple.infoflow.android.callbacks.filters.ApplicationCallbackFilter;
import soot.jimple.infoflow.android.callbacks.filters.UnreachableConstructorFilter;
import soot.jimple.infoflow.android.callbacks.xml.CollectedCallbacks;
import soot.jimple.infoflow.android.callbacks.xml.CollectedCallbacksSerializer;
import soot.jimple.infoflow.android.config.SootConfigForAndroid;
import soot.jimple.infoflow.android.data.AndroidMemoryManager;
import soot.jimple.infoflow.android.data.AndroidMethod;
import soot.jimple.infoflow.android.data.parsers.PermissionMethodParser;
import soot.jimple.infoflow.android.entryPointCreators.AndroidEntryPointCreator;
import soot.jimple.infoflow.android.entryPointCreators.components.ComponentEntryPointCollection;
import soot.jimple.infoflow.android.iccta.IccInstrumenter;
import soot.jimple.infoflow.android.manifest.IAndroidApplication;
import soot.jimple.infoflow.android.manifest.IManifestHandler;
import soot.jimple.infoflow.android.manifest.ProcessManifest;
import soot.jimple.infoflow.android.resources.ARSCFileParser;
import soot.jimple.infoflow.android.resources.ARSCFileParser.AbstractResource;
import soot.jimple.infoflow.android.resources.ARSCFileParser.StringResource;
import soot.jimple.infoflow.android.resources.LayoutFileParser;
import soot.jimple.infoflow.android.resources.controls.AndroidLayoutControl;
import soot.jimple.infoflow.android.results.xml.InfoflowResultsSerializer;
import soot.jimple.infoflow.android.source.AccessPathBasedSourceSinkManager;
import soot.jimple.infoflow.android.source.ConfigurationBasedCategoryFilter;
import soot.jimple.infoflow.android.source.UnsupportedSourceSinkFormatException;
import soot.jimple.infoflow.android.source.parsers.xml.XMLSourceSinkParser;
import soot.jimple.infoflow.cfg.BiDirICFGFactory;
import soot.jimple.infoflow.cfg.LibraryClassPatcher;
import soot.jimple.infoflow.config.IInfoflowConfig;
import soot.jimple.infoflow.data.Abstraction;
import soot.jimple.infoflow.data.FlowDroidMemoryManager.PathDataErasureMode;
import soot.jimple.infoflow.entryPointCreators.SimulatedCodeElementTag;
import soot.jimple.infoflow.handlers.PostAnalysisHandler;
import soot.jimple.infoflow.handlers.PreAnalysisHandler;
import soot.jimple.infoflow.handlers.ResultsAvailableHandler;
import soot.jimple.infoflow.handlers.TaintPropagationHandler;
import soot.jimple.infoflow.ipc.IIPCManager;
import soot.jimple.infoflow.memory.FlowDroidMemoryWatcher;
import soot.jimple.infoflow.memory.FlowDroidTimeoutWatcher;
import soot.jimple.infoflow.memory.IMemoryBoundedSolver;
import soot.jimple.infoflow.results.InfoflowPerformanceData;
import soot.jimple.infoflow.results.InfoflowResults;
import soot.jimple.infoflow.rifl.RIFLSourceSinkDefinitionProvider;
import soot.jimple.infoflow.river.IUsageContextProvider;
import soot.jimple.infoflow.solver.cfg.IInfoflowCFG;
import soot.jimple.infoflow.solver.memory.IMemoryManager;
import soot.jimple.infoflow.solver.memory.IMemoryManagerFactory;
import soot.jimple.infoflow.sourcesSinks.definitions.ISourceSinkDefinition;
import soot.jimple.infoflow.sourcesSinks.definitions.ISourceSinkDefinitionProvider;
import soot.jimple.infoflow.sourcesSinks.definitions.MethodSourceSinkDefinition;
import soot.jimple.infoflow.sourcesSinks.manager.ISourceSinkManager;
import soot.jimple.infoflow.taintWrappers.ITaintPropagationWrapper;
import soot.jimple.infoflow.taintWrappers.ITaintWrapperDataFlowAnalysis;
import soot.jimple.infoflow.util.SystemClassHandler;
import soot.jimple.infoflow.values.IValueProvider;
import soot.options.Options;
import soot.util.HashMultiMap;
import soot.util.MultiMap;

public class SetupApplication implements ITaintWrapperDataFlowAnalysis {

	private final Logger logger = LoggerFactory.getLogger(getClass());

	protected ISourceSinkDefinitionProvider sourceSinkProvider;
	protected MultiMap callbackMethods = new HashMultiMap<>();
	protected MultiMap fragmentClasses = new HashMultiMap<>();

	protected InfoflowAndroidConfiguration config = new InfoflowAndroidConfiguration();

	protected Set entrypoints = null;
	protected MultiMap javascriptInterfaceStmts = new HashMultiMap<>();
	protected Set callbackClasses = null;
	protected AndroidEntryPointCreator entryPointCreator = null;
	protected IccInstrumenter iccInstrumenter = null;

	protected ARSCFileParser resources = null;
	protected IManifestHandler manifest = null;
	protected IValueProvider valueProvider = null;

	protected final boolean forceAndroidJar;
	protected ITaintPropagationWrapper taintWrapper;

	protected ISourceSinkManager sourceSinkManager = null;

	protected IInfoflowConfig sootConfig = new SootConfigForAndroid();
	protected BiDirICFGFactory cfgFactory = null;

	protected IIPCManager ipcManager = null;

	protected Set collectedSources = null;
	protected Set collectedSinks = null;

	protected String callbackFile = "AndroidCallbacks.txt";
	protected SootClass scView = null;

	protected Set preprocessors = new HashSet<>();
	protected Set resultsAvailableHandlers = new HashSet<>();
	protected TaintPropagationHandler taintPropagationHandler = null;
	protected TaintPropagationHandler aliasPropagationHandler = null;

	protected IUsageContextProvider usageContextProvider = null;

	protected IInPlaceInfoflow infoflow = null;

	/**
	 * Class for aggregating the data flow results obtained through multiple runs of
	 * the data flow solver.
	 * 
	 * @author Steven Arzt
	 *
	 */
	private static class MultiRunResultAggregator implements ResultsAvailableHandler {

		private final InfoflowResults aggregatedResults;
		private InfoflowResults lastResults = null;
		private IInfoflowCFG lastICFG = null;

		public MultiRunResultAggregator(boolean pathAgnosticResults) {
			aggregatedResults = new InfoflowResults(pathAgnosticResults);
		}

		@Override
		public void onResultsAvailable(IInfoflowCFG cfg, InfoflowResults results) {
			this.aggregatedResults.addAll(results);
			this.lastResults = results;
			this.lastICFG = cfg;
		}

		/**
		 * Gets all data flow results aggregated so far
		 * 
		 * @return All data flow results aggregated so far
		 */
		public InfoflowResults getAggregatedResults() {
			return this.aggregatedResults;
		}

		/**
		 * Gets the total number of source-to-sink connections from the last partial
		 * result that was added to this aggregator
		 * 
		 * @return The results from the last run of the data flow analysis
		 */
		public InfoflowResults getLastResults() {
			return this.lastResults;
		}

		/**
		 * Clears the stored result set from the last data flow run
		 */
		public void clearLastResults() {
			this.lastResults = null;
			this.lastICFG = null;
		}

		/**
		 * Gets the ICFG that was returned together with the last set of data flow
		 * results
		 * 
		 * @return The ICFG that was returned together with the last set of data flow
		 *         results
		 */
		public IInfoflowCFG getLastICFG() {
			return this.lastICFG;
		}

	}

	/**
	 * Creates a new instance of the {@link SetupApplication} class
	 * 
	 * @param config The data flow configuration to use
	 */
	public SetupApplication(InfoflowAndroidConfiguration config) {
		this(config, null);
	}

	/**
	 * Creates a new instance of the {@link SetupApplication} class
	 * 
	 * @param androidJar      The path to the Android SDK's "platforms" directory if
	 *                        Soot shall automatically select the JAR file to be
	 *                        used or the path to a single JAR file to force one.
	 * @param apkFileLocation The path to the APK file to be analyzed
	 */
	public SetupApplication(String androidJar, String apkFileLocation) {
		this(getConfig(androidJar, apkFileLocation));
	}

	/**
	 * Creates a new instance of the {@link SetupApplication} class
	 * 
	 * @param androidJar      The path to the Android SDK's "platforms" directory if
	 *                        Soot shall automatically select the JAR file to be
	 *                        used or the path to a single JAR file to force one.
	 * @param apkFileLocation The path to the APK file to be analyzed
	 * @param ipcManager      The IPC manager to use for modeling inter-component
	 *                        and inter-application data flows
	 */
	public SetupApplication(String androidJar, String apkFileLocation, IIPCManager ipcManager) {
		this(getConfig(androidJar, apkFileLocation), ipcManager);
	}

	/**
	 * Creates a basic data flow configuration with only the input files set
	 * 
	 * @param androidJar      The path to the Android SDK's "platforms" directory if
	 *                        Soot shall automatically select the JAR file to be
	 *                        used or the path to a single JAR file to force one.
	 * @param apkFileLocation The path to the APK file to be analyzed
	 * @return The new configuration
	 */
	private static InfoflowAndroidConfiguration getConfig(String androidJar, String apkFileLocation) {
		InfoflowAndroidConfiguration config = new InfoflowAndroidConfiguration();
		config.getAnalysisFileConfig().setTargetAPKFile(apkFileLocation);
		config.getAnalysisFileConfig().setAndroidPlatformDir(androidJar);
		return config;
	}

	/**
	 * Creates a new instance of the {@link SetupApplication} class
	 * 
	 * @param config     The data flow configuration to use
	 * @param ipcManager The IPC manager to use for modelling inter-component and
	 *                   inter-application data flows
	 */
	public SetupApplication(InfoflowAndroidConfiguration config, IIPCManager ipcManager) {
		this.config = config;
		this.ipcManager = ipcManager;

		// We can use either a specific platform JAR file or automatically
		// select the right one
		if (config.getSootIntegrationMode() == SootIntegrationMode.CreateNewInstance) {
			String platformDir = config.getAnalysisFileConfig().getAndroidPlatformDir();
			if (platformDir == null || platformDir.isEmpty())
				throw new RuntimeException("Android platform directory not specified");
			File f = new File(platformDir);
			this.forceAndroidJar = f.isFile();
		} else {
			this.forceAndroidJar = false;
		}
	}

	/**
	 * Gets the set of sinks loaded into FlowDroid These are the sinks as they are
	 * defined through the SourceSinkManager.
	 * 
	 * @return The set of sinks loaded into FlowDroid
	 */
	public Collection getSinks() {
		return this.sourceSinkProvider == null ? null : this.sourceSinkProvider.getSinks();
	}

	/**
	 * Gets the concrete instances of sinks that have been collected inside the app.
	 * This method returns null if source and sink logging has not been enabled (see
	 * InfoflowConfiguration.setLogSourcesAndSinks()).
	 * 
	 * @return The set of concrete sink instances in the app
	 */
	public Set getCollectedSinks() {
		return collectedSinks;
	}

	/**
	 * Prints the list of sinks registered with FlowDroud to stdout
	 */
	public void printSinks() {
		if (this.sourceSinkProvider == null) {
			logger.error("Sinks not calculated yet");
			return;
		}
		logger.info("Sinks:");
		for (ISourceSinkDefinition am : getSinks()) {
			logger.info(String.format("- %s", am.toString()));
		}
		logger.info("End of Sinks");
	}

	/**
	 * Gets the set of sources loaded into FlowDroid. These are the sources as they
	 * are defined through the SourceSinkManager.
	 * 
	 * @return The set of sources loaded into FlowDroid
	 */
	public Collection getSources() {
		return this.sourceSinkProvider == null ? null : this.sourceSinkProvider.getSources();
	}

	/**
	 * Gets the concrete instances of sources that have been collected inside the
	 * app. This method returns null if source and sink logging has not been enabled
	 * (see InfoflowConfiguration.setLogSourcesAndSinks()).
	 * 
	 * @return The set of concrete source instances in the app
	 */
	public Set getCollectedSources() {
		return collectedSources;
	}

	/**
	 * Prints the list of sources registered with FlowDroud to stdout
	 */
	public void printSources() {
		if (this.sourceSinkProvider == null) {
			logger.error("Sources not calculated yet");
			return;
		}
		logger.info("Sources:");
		for (ISourceSinkDefinition am : getSources()) {
			logger.info(String.format("- %s", am.toString()));
		}
		logger.info("End of Sources");
	}

	/**
	 * Gets the set of classes containing entry point methods for the lifecycle
	 * 
	 * @return The set of classes containing entry point methods for the lifecycle
	 */
	public Set getEntrypointClasses() {
		return entrypoints;
	}

	/**
	 * Prints list of classes containing entry points to stdout
	 */
	public void printEntrypoints() {
		if (this.entrypoints == null)
			logger.error("Entry points not initialized");
		else {
			logger.info("Classes containing entry points:");
			for (SootClass sc : entrypoints)
				logger.info("\t" + sc.getName());
			logger.info("End of Entrypoints");
		}
	}

	/**
	 * Sets the class names of callbacks. hese are the callback classes defined by
	 * the Android SDK, and they are not mapped to the current application. If this
	 * value is null, FlowDroid automatically loads the names from
	 * AndroidCallbacks.txt as the default behavior.
	 * 
	 * @param callbackClasses The class names of callbacks or null to use the
	 *                        default file.
	 */
	public void setCallbackClasses(Set callbackClasses) {
		this.callbackClasses = callbackClasses;
	}

	/**
	 * Gets the class names of callbacks. These are the callback classes defined by
	 * the Android SDK, and they are not mapped to the current application.
	 * 
	 * @return The class names of callbacks or null to use the default file.
	 */
	public Set getCallbackClasses() {
		return callbackClasses;
	}

	@Override
	public void setTaintWrapper(ITaintPropagationWrapper taintWrapper) {
		this.taintWrapper = taintWrapper;
	}

	@Override
	public ITaintPropagationWrapper getTaintWrapper() {
		return this.taintWrapper;
	}

	/**
	 * Parses common app resources such as the manifest file
	 * 
	 * @throws IOException            Thrown if the given source/sink file could not
	 *                                be read.
	 * @throws XmlPullParserException Thrown if the Android manifest file could not
	 *                                be read.
	 */
	protected void parseAppResources() throws IOException, XmlPullParserException {
		final File targetAPK = new File(config.getAnalysisFileConfig().getTargetAPKFile());
		if (!targetAPK.exists())
			throw new RuntimeException(
					String.format("Target APK file %s does not exist", targetAPK.getCanonicalPath()));

		// Parse the resource file
		long beforeARSC = System.nanoTime();
		this.resources = new ARSCFileParser();
		this.resources.parse(targetAPK.getAbsolutePath());
		logger.info("ARSC file parsing took " + (System.nanoTime() - beforeARSC) / 1E9 + " seconds");

		// To look for callbacks, we need to start somewhere. We use the Android
		// lifecycle methods for this purpose.
		this.manifest = createManifestParser(targetAPK);
		SystemClassHandler.v().setExcludeSystemComponents(config.getIgnoreFlowsInSystemPackages());
		Set entryPoints = manifest.getEntryPointClasses();
		this.entrypoints = new HashSet<>(entryPoints.size());
		for (String className : entryPoints) {
			SootClass sc = Scene.v().getSootClassUnsafe(className);
			if (sc != null)
				this.entrypoints.add(sc);
		}
	}

	/**
	 * Creates the manifest handler for processing the app manifest
	 * 
	 * @param targetAPK The target APK file
	 * @return The manifest handler for reading the given app's manifest file
	 * @throws IOException
	 * @throws XmlPullParserException
	 */
	protected IManifestHandler createManifestParser(final File targetAPK) throws IOException, XmlPullParserException {
		return new ProcessManifest(targetAPK, resources);
	}

	/**
	 * Calculates the sets of sources, sinks, entry points, and callbacks methods
	 * for the given APK file.
	 * 
	 * @param sourcesAndSinks A provider from which the analysis can obtain the list
	 *                        of sources and sinks
	 * @throws IOException            Thrown if the given source/sink file could not
	 *                                be read.
	 * @throws XmlPullParserException Thrown if the Android manifest file could not
	 *                                be read.
	 */
	private void calculateCallbacks(ISourceSinkDefinitionProvider sourcesAndSinks)
			throws IOException, XmlPullParserException {
		calculateCallbacks(sourcesAndSinks, null);
	}

	/**
	 * Calculates the sets of sources, sinks, entry points, and callbacks methods
	 * for the entry point in the given APK file.
	 * 
	 * @param sourcesAndSinks A provider from which the analysis can obtain the list
	 *                        of sources and sinks
	 * @param entryPoint      The entry point for which to calculate the callbacks.
	 *                        Pass null to calculate callbacks for all entry points.
	 * @throws IOException            Thrown if the given source/sink file could not
	 *                                be read.
	 * @throws XmlPullParserException Thrown if the Android manifest file could not
	 *                                be read.
	 */
	private void calculateCallbacks(ISourceSinkDefinitionProvider sourcesAndSinks, SootClass entryPoint)
			throws IOException, XmlPullParserException {
		// Add the callback methods
		LayoutFileParser lfp = null;
		final CallbackConfiguration callbackConfig = config.getCallbackConfig();
		if (callbackConfig.getEnableCallbacks()) {
			// If we have a callback file, we use it
			String callbackFile = callbackConfig.getCallbacksFile();
			if (callbackFile != null && !callbackFile.isEmpty()) {
				File cbFile = new File(callbackFile);
				if (cbFile.exists()) {
					CollectedCallbacks callbacks = CollectedCallbacksSerializer.deserialize(callbackConfig);
					if (callbacks != null) {
						// Get our callback data from the file
						entrypoints = callbacks.getEntryPoints();
						fragmentClasses = callbacks.getFragmentClasses();
						callbackMethods = callbacks.getCallbackMethods();

						// Create the callgraph
						createMainMethod(entryPoint);
						constructCallgraphInternal();

						createSourceSinkProvider(entryPoint, lfp);
						return;
					}
				}
			}

			if (callbackClasses != null && callbackClasses.isEmpty()) {
				logger.warn("Callback definition file is empty, disabling callbacks");
			} else {
				lfp = createLayoutFileParser();
				switch (callbackConfig.getCallbackAnalyzer()) {
				case Fast:
					calculateCallbackMethodsFast(lfp, entryPoint);
					break;
				case Default:
					calculateCallbackMethods(lfp, entryPoint);
					break;
				default:
					throw new RuntimeException("Unknown callback analyzer");
				}
			}
		} else if (config.getSootIntegrationMode().needsToBuildCallgraph()) {
			// Create the new iteration of the main method
			createMainMethod(entryPoint);
			constructCallgraphInternal();
		}

		logger.info("Entry point calculation done.");
		createSourceSinkProvider(entryPoint, lfp);
	}

	/**
	 * Creates the source/sink provider
	 * 
	 * @param entryPoint The entry point on which to run the data flow analysis
	 * @param lfp        The layout file parser
	 */
	protected void createSourceSinkProvider(SootClass entryPoint, LayoutFileParser lfp) {
		if (this.sourceSinkProvider != null) {
			// Get the callbacks for the current entry point
			Set callbacks;
			if (entryPoint == null)
				callbacks = this.callbackMethods.values();
			else
				callbacks = this.callbackMethods.get(entryPoint);

			// Create the SourceSinkManager
			sourceSinkManager = createSourceSinkManager(lfp, callbacks);
		}
	}

	/**
	 * Creates a new layout file parser. Derived classes can override this method to
	 * supply their own parser.
	 * 
	 * @return The newly created layout file parser.
	 */
	protected LayoutFileParser createLayoutFileParser() {
		return new LayoutFileParser(this.manifest.getPackageName(), this.resources);
	}

	/**
	 * Creates an instance of {@link ISourceSinkManager} that defines what FlowDorid
	 * shall consider as a source or sink, respectively.
	 * 
	 * @param lfp       The parser that handles the layout XML files
	 * @param callbacks The callbacks that have been collected so far
	 * @return The new source sink manager
	 */
	protected ISourceSinkManager createSourceSinkManager(LayoutFileParser lfp,
			Set callbacks) {
		AccessPathBasedSourceSinkManager sourceSinkManager = new AccessPathBasedSourceSinkManager(
				this.sourceSinkProvider.getSources(), this.sourceSinkProvider.getSinks(), callbacks, config,
				lfp == null ? null : lfp.getUserControlsByID());

		sourceSinkManager.setAppPackageName(this.manifest.getPackageName());
		sourceSinkManager.setResourcePackages(this.resources.getPackages());
		return sourceSinkManager;
	}

	/**
	 * Triggers the callgraph construction in Soot
	 */
	protected void constructCallgraphInternal() {
		// If we are configured to use an existing callgraph, we may not replace
		// it. However, we must make sure that there really is one.
		if (config.getSootIntegrationMode() == SootIntegrationMode.UseExistingCallgraph) {
			if (!Scene.v().hasCallGraph())
				throw new RuntimeException("FlowDroid is configured to use an existing callgraph, but there is none");
			return;
		}

		// Do we need ICC instrumentation?
		if (config.getIccConfig().isIccEnabled()) {
			if (iccInstrumenter == null)
				iccInstrumenter = createIccInstrumenter();
			iccInstrumenter.onBeforeCallgraphConstruction();
		}

		// Run the preprocessors
		for (PreAnalysisHandler handler : this.preprocessors)
			handler.onBeforeCallgraphConstruction();

		// Make sure that we don't have any weird leftovers
		releaseCallgraph();

		// If we didn't create the Soot instance, we can't be sure what its callgraph
		// configuration is
		if (config.getSootIntegrationMode() == SootIntegrationMode.UseExistingInstance)
			configureCallgraph();

		// Construct the actual callgraph
		logger.info("Constructing the callgraph...");
		PackManager.v().getPack("cg").apply();

		// ICC instrumentation
		if (iccInstrumenter != null)
			iccInstrumenter.onAfterCallgraphConstruction();

		// Run the preprocessors
		for (PreAnalysisHandler handler : this.preprocessors)
			handler.onAfterCallgraphConstruction();

		// Make sure that we have a hierarchy
		Scene.v().getOrMakeFastHierarchy();
	}

	/**
	 * Creates the ICC instrumentation class
	 * 
	 * @return An instance of the class for instrumenting the app's code for
	 *         inter-component communication
	 */
	protected IccInstrumenter createIccInstrumenter() {
		IccInstrumenter iccInstrumenter;
		iccInstrumenter = new IccInstrumenter(config.getIccConfig().getIccModel(),
				entryPointCreator.getGeneratedMainMethod().getDeclaringClass(),
				entryPointCreator.getComponentToEntryPointInfo());
		return iccInstrumenter;
	}

	/**
	 * Calculates the set of callback methods declared in the XML resource files or
	 * the app's source code
	 * 
	 * @param lfp       The layout file parser to be used for analyzing UI controls
	 * @param component The Android component for which to compute the callbacks.
	 *                  Pass null to compute callbacks for all components.
	 * @throws IOException Thrown if a required configuration cannot be read
	 */
	private void calculateCallbackMethods(LayoutFileParser lfp, SootClass component) throws IOException {
		final CallbackConfiguration callbackConfig = config.getCallbackConfig();

		// Load the APK file
		if (config.getSootIntegrationMode().needsToBuildCallgraph())
			releaseCallgraph();

		// Make sure that we don't have any leftovers from previous runs
		PackManager.v().getPack("wjtp").remove("wjtp.lfp");
		PackManager.v().getPack("wjtp").remove("wjtp.ajc");

		// Get the classes for which to find callbacks
		Set entryPointClasses = getComponentsToAnalyze(component);

		// Collect the callback interfaces implemented in the app's
		// source code. Note that the filters should know all components to
		// filter out callbacks even if the respective component is only
		// analyzed later.
		AbstractCallbackAnalyzer jimpleClass = callbackClasses == null
				? new DefaultCallbackAnalyzer(config, entryPointClasses, callbackMethods, callbackFile)
				: new DefaultCallbackAnalyzer(config, entryPointClasses, callbackMethods, callbackClasses);
		if (valueProvider != null)
			jimpleClass.setValueProvider(valueProvider);
		jimpleClass.addCallbackFilter(new AlienHostComponentFilter(entrypoints));
		jimpleClass.addCallbackFilter(new ApplicationCallbackFilter(entrypoints));
		jimpleClass.addCallbackFilter(new UnreachableConstructorFilter());
		jimpleClass.collectCallbackMethods();

		// Find the user-defined sources in the layout XML files. This
		// only needs to be done once, but is a Soot phase.
		lfp.parseLayoutFile(config.getAnalysisFileConfig().getTargetAPKFile());

		// Watch the callback collection algorithm's memory consumption
		FlowDroidMemoryWatcher memoryWatcher = null;
		FlowDroidTimeoutWatcher timeoutWatcher = null;
		if (jimpleClass instanceof IMemoryBoundedSolver) {
			// Make sure that we don't spend too much time and memory in the callback
			// analysis
			memoryWatcher = createCallbackMemoryWatcher(jimpleClass);
			timeoutWatcher = createCallbackTimeoutWatcher(callbackConfig, jimpleClass);
		}

		try {
			int depthIdx = 0;
			boolean hasChanged = true;
			boolean isInitial = true;
			while (hasChanged) {
				hasChanged = false;

				// Check whether the solver has been aborted in the meantime
				if (jimpleClass instanceof IMemoryBoundedSolver) {
					if (((IMemoryBoundedSolver) jimpleClass).isKilled())
						break;
				}

				// Create the new iteration of the main method
				createMainMethod(component);

				int numPrevEdges = 0;
				if (Scene.v().hasCallGraph()) {
					numPrevEdges = Scene.v().getCallGraph().size();
				}
				// Since the generation of the main method can take some time,
				// we check again whether we need to stop.
				if (jimpleClass instanceof IMemoryBoundedSolver) {
					if (((IMemoryBoundedSolver) jimpleClass).isKilled()) {
						logger.warn("Callback calculation aborted due to timeout");
						break;
					}
				}

				if (!isInitial) {
					// Reset the callgraph
					releaseCallgraph();

					// We only want to parse the layout files once
					PackManager.v().getPack("wjtp").remove("wjtp.lfp");
				}
				isInitial = false;

				// Run the soot-based operations
				constructCallgraphInternal();
				if (!Scene.v().hasCallGraph())
					throw new RuntimeException("No callgraph in Scene even after creating one. That's very sad "
							+ "and should never happen.");

				lfp.parseLayoutFileDirect(config.getAnalysisFileConfig().getTargetAPKFile());
				PackManager.v().getPack("wjtp").apply();

				// Creating all callgraph takes time and memory. Check whether
				// the solver has been aborted in the meantime
				if (jimpleClass instanceof IMemoryBoundedSolver) {
					if (((IMemoryBoundedSolver) jimpleClass).isKilled()) {
						logger.warn("Aborted callback collection because of low memory");
						break;
					}
				}

				if (numPrevEdges < Scene.v().getCallGraph().size())
					hasChanged = true;

				// Collect the results of the soot-based phases
				if (this.callbackMethods.putAll(jimpleClass.getCallbackMethods()))
					hasChanged = true;

				if (entrypoints.addAll(jimpleClass.getDynamicManifestComponents()))
					hasChanged = true;

				if (javascriptInterfaceStmts.putAll(jimpleClass.getJavaScriptInterfaces()))
					hasChanged = true;

				// Collect the XML-based callback methods
				if (collectXmlBasedCallbackMethods(lfp, jimpleClass))
					hasChanged = true;

				// Avoid callback overruns. If we are beyond the callback limit
				// for one entry point, we may not collect any further callbacks
				// for that entry point.
				if (callbackConfig.getMaxCallbacksPerComponent() > 0) {
					for (Iterator componentIt = this.callbackMethods.keySet().iterator(); componentIt
							.hasNext();) {
						SootClass callbackComponent = componentIt.next();
						if (this.callbackMethods.get(callbackComponent).size() > callbackConfig
								.getMaxCallbacksPerComponent()) {
							componentIt.remove();
							jimpleClass.excludeEntryPoint(callbackComponent);
						}
					}
				}

				// Check depth limiting
				depthIdx++;
				if (callbackConfig.getMaxAnalysisCallbackDepth() > 0
						&& depthIdx >= callbackConfig.getMaxAnalysisCallbackDepth())
					break;

				// If we work with an existing callgraph, the callgraph never
				// changes and thus it doesn't make any sense to go multiple
				// rounds
				if (config.getSootIntegrationMode() == SootIntegrationMode.UseExistingCallgraph)
					break;
			}
		} catch (Exception ex) {
			logger.error("Could not calculate callback methods", ex);
			throw ex;
		} finally {
			// Shut down the watchers
			if (timeoutWatcher != null)
				timeoutWatcher.stop();
			if (memoryWatcher != null)
				memoryWatcher.close();
		}

		// Filter out callbacks that belong to fragments that are not used by
		// the host activity
		AlienFragmentFilter fragmentFilter = new AlienFragmentFilter(invertMap(fragmentClasses));
		fragmentFilter.reset();
		for (Iterator> cbIt = this.callbackMethods.iterator(); cbIt
				.hasNext();) {
			Pair pair = cbIt.next();

			// Check whether the filter accepts the given mapping
			if (!fragmentFilter.accepts(pair.getO1(), pair.getO2().getTargetMethod()))
				cbIt.remove();
			else if (!fragmentFilter.accepts(pair.getO1(), pair.getO2().getTargetMethod().getDeclaringClass())) {
				cbIt.remove();
			}
		}

		// Avoid callback overruns
		if (callbackConfig.getMaxCallbacksPerComponent() > 0) {
			for (Iterator componentIt = this.callbackMethods.keySet().iterator(); componentIt.hasNext();) {
				SootClass callbackComponent = componentIt.next();
				if (this.callbackMethods.get(callbackComponent).size() > callbackConfig.getMaxCallbacksPerComponent())
					componentIt.remove();
			}
		}

		// Make sure that we don't retain any weird Soot phases
		PackManager.v().getPack("wjtp").remove("wjtp.lfp");
		PackManager.v().getPack("wjtp").remove("wjtp.ajc");

		// Warn the user if we had to abort the callback analysis early
		boolean abortedEarly = false;
		if (jimpleClass instanceof IMemoryBoundedSolver) {
			if (((IMemoryBoundedSolver) jimpleClass).isKilled()) {
				logger.warn("Callback analysis aborted early due to time or memory exhaustion");
				abortedEarly = true;
			}
		}
		if (!abortedEarly)
			logger.info("Callback analysis terminated normally");

		// Serialize the callbacks
		if (callbackConfig.isSerializeCallbacks()) {
			CollectedCallbacks callbacks = new CollectedCallbacks(entryPointClasses, callbackMethods, fragmentClasses);
			CollectedCallbacksSerializer.serialize(callbacks, callbackConfig);
		}
	}

	/**
	 * Creates the memory watcher for aborting the callback analysis in case it runs
	 * out of memory. This method also starts the watchdog thread. Derived classes
	 * can implement their own timeout handling if necessary.
	 * 
	 * @param callbackConfig The configuration for the callback analysis
	 * @param analyzer       The callback analyzer
	 * @return The memory watcher that keeps track of the amount of memory spent in
	 *         the callback analysis
	 */
	protected FlowDroidMemoryWatcher createCallbackMemoryWatcher(AbstractCallbackAnalyzer jimpleClass) {
		FlowDroidMemoryWatcher memoryWatcher = new FlowDroidMemoryWatcher(config.getMemoryThreshold());
		memoryWatcher.addSolver((IMemoryBoundedSolver) jimpleClass);
		return memoryWatcher;
	}

	/**
	 * Creates the timeout watcher for aborting the callback analysis in case it
	 * runs out of time. This method also starts the watchdog thread. Derived
	 * classes can implement their own timeout handling if necessary.
	 * 
	 * @param callbackConfig The configuration for the callback analysis
	 * @param analyzer       The callback analyzer
	 * @return The timeout watcher that keeps track of the time spent in the
	 *         callback analysis
	 */
	protected FlowDroidTimeoutWatcher createCallbackTimeoutWatcher(final CallbackConfiguration callbackConfig,
			AbstractCallbackAnalyzer analyzer) {
		if (callbackConfig.getCallbackAnalysisTimeout() > 0) {
			FlowDroidTimeoutWatcher timeoutWatcher = new FlowDroidTimeoutWatcher(
					callbackConfig.getCallbackAnalysisTimeout());
			timeoutWatcher.addSolver((IMemoryBoundedSolver) analyzer);
			timeoutWatcher.start();
			return timeoutWatcher;
		}
		return null;
	}

	/**
	 * Inverts the given {@link MultiMap}. The keys become values and vice versa
	 * 
	 * @param original The map to invert
	 * @return An inverted copy of the given map
	 */
	private  MultiMap invertMap(MultiMap original) {
		MultiMap newTag = new HashMultiMap<>();
		for (V key : original.keySet())
			for (K value : original.get(key))
				newTag.put(value, key);
		return newTag;
	}

	/**
	 * Releases the callgraph and all intermediate objects associated with it
	 */
	protected void releaseCallgraph() {
		// If we are configured to use an existing callgraph, we may not release
		// it
		if (config.getSootIntegrationMode() == SootIntegrationMode.UseExistingCallgraph)
			return;

		Scene.v().releaseCallGraph();
		Scene.v().releasePointsToAnalysis();
		Scene.v().releaseReachableMethods();
		G.v().resetSpark();
	}

	/**
	 * Collects the XML-based callback methods, e.g., Button.onClick() declared in
	 * layout XML files
	 * 
	 * @param lfp         The layout file parser
	 * @param jimpleClass The analysis class that gives us a mapping between layout
	 *                    IDs and components
	 * @return True if at least one new callback method has been added, otherwise
	 *         false
	 */
	private boolean collectXmlBasedCallbackMethods(LayoutFileParser lfp, AbstractCallbackAnalyzer jimpleClass) {
		SootMethod smViewOnClick = Scene.v()
				.grabMethod("");

		// Collect the XML-based callback methods
		boolean hasNewCallback = false;
		for (final SootClass callbackClass : jimpleClass.getLayoutClasses().keySet()) {
			if (jimpleClass.isExcludedEntryPoint(callbackClass))
				continue;

			Set classIds = jimpleClass.getLayoutClasses().get(callbackClass);
			for (Integer classId : classIds) {
				AbstractResource resource = this.resources.findResource(classId);
				if (resource instanceof StringResource) {
					final String layoutFileName = ((StringResource) resource).getValue();

					// Add the callback methods for the given class
					Set callbackMethods = lfp.getCallbackMethods().get(layoutFileName);
					if (callbackMethods != null) {
						for (String methodName : callbackMethods) {
							final String subSig = "void " + methodName + "(android.view.View)";

							// The callback may be declared directly in the
							// class or in one of the superclasses
							SootClass currentClass = callbackClass;
							while (true) {
								SootMethod callbackMethod = currentClass.getMethodUnsafe(subSig);
								if (callbackMethod != null) {
									if (this.callbackMethods.put(callbackClass, new AndroidCallbackDefinition(
											callbackMethod, smViewOnClick, CallbackType.Widget)))
										hasNewCallback = true;
									break;
								}

								SootClass sclass = currentClass.getSuperclassUnsafe();
								if (sclass == null) {
									logger.error(String.format("Callback method %s not found in class %s", methodName,
											callbackClass.getName()));
									break;
								}
								currentClass = sclass;
							}
						}
					}

					// Add the fragments for this class
					Set fragments = lfp.getFragments().get(layoutFileName);
					if (fragments != null) {
						for (SootClass fragment : fragments) {
							if (fragmentClasses.put(callbackClass, fragment))
								hasNewCallback = true;
						}
					}

					// For user-defined views, we need to emulate their
					// callbacks
					Set controls = lfp.getUserControls().get(layoutFileName);
					if (controls != null) {
						for (AndroidLayoutControl lc : controls) {
							if (!SystemClassHandler.v().isClassInSystemPackage(lc.getViewClass().getName()))
								hasNewCallback |= registerCallbackMethodsForView(callbackClass, lc);
						}
					}
				} else
					logger.error("Unexpected resource type for layout class");
			}
		}

		// Collect the fragments, merge the fragments created in the code with
		// those declared in Xml files
		if (fragmentClasses.putAll(jimpleClass.getFragmentClasses())) // Fragments
																		// declared
																		// in
																		// code
			hasNewCallback = true;

		return hasNewCallback;
	}

	/**
	 * Calculates the set of callback methods declared in the XML resource files or
	 * the app's source code. This method prefers performance over precision and
	 * scans the code including unreachable methods.
	 * 
	 * @param lfp        The layout file parser to be used for analyzing UI controls
	 * @param entryPoint The entry point for which to calculate the callbacks. Pass
	 *                   null to calculate callbacks for all entry points.
	 * @throws IOException Thrown if a required configuration cannot be read
	 */
	private void calculateCallbackMethodsFast(LayoutFileParser lfp, SootClass component) throws IOException {
		// Construct the current callgraph
		releaseCallgraph();
		createMainMethod(component);
		constructCallgraphInternal();

		// Get the classes for which to find callbacks
		Set entryPointClasses = getComponentsToAnalyze(component);

		// Collect the callback interfaces implemented in the app's
		// source code
		AbstractCallbackAnalyzer jimpleClass = callbackClasses == null
				? new FastCallbackAnalyzer(config, entryPointClasses, callbackFile)
				: new FastCallbackAnalyzer(config, entryPointClasses, callbackClasses);
		if (valueProvider != null)
			jimpleClass.setValueProvider(valueProvider);
		jimpleClass.collectCallbackMethods();

		// Collect the results
		this.callbackMethods.putAll(jimpleClass.getCallbackMethods());
		this.entrypoints.addAll(jimpleClass.getDynamicManifestComponents());

		// Find the user-defined sources in the layout XML files. This
		// only needs to be done once, but is a Soot phase.
		lfp.parseLayoutFileDirect(config.getAnalysisFileConfig().getTargetAPKFile());

		// Collect the XML-based callback methods
		collectXmlBasedCallbackMethods(lfp, jimpleClass);

		// Construct the final callgraph
		releaseCallgraph();
		createMainMethod(component);
		constructCallgraphInternal();
	}

	/**
	 * Registers the callback methods in the given layout control so that they are
	 * included in the dummy main method
	 * 
	 * @param callbackClass The class with which to associate the layout callbacks
	 * @param lc            The layout control whose callbacks are to be associated
	 *                      with the given class
	 * @return
	 */
	private boolean registerCallbackMethodsForView(SootClass callbackClass, AndroidLayoutControl lc) {
		// Ignore system classes
		if (SystemClassHandler.v().isClassInSystemPackage(callbackClass.getName()))
			return false;

		// Get common Android classes
		if (scView == null)
			scView = Scene.v().getSootClass("android.view.View");

		// Check whether the current class is actually a view
		if (!Scene.v().getOrMakeFastHierarchy().canStoreType(lc.getViewClass().getType(), scView.getType()))
			return false;

		// There are also some classes that implement interesting callback
		// methods.
		// We model this as follows: Whenever the user overwrites a method in an
		// Android OS class, we treat it as a potential callback.
		SootClass sc = lc.getViewClass();
		Map systemMethods = new HashMap<>(10000);
		for (SootClass parentClass : Scene.v().getActiveHierarchy().getSuperclassesOf(sc)) {
			if (parentClass.getName().startsWith("android."))
				for (SootMethod sm : parentClass.getMethods())
					if (!sm.isConstructor())
						systemMethods.put(sm.getSubSignature(), sm);
		}

		boolean changed = false;
		// Scan for methods that overwrite parent class methods
		for (SootMethod sm : sc.getMethods()) {
			if (!sm.isConstructor()) {
				SootMethod parentMethod = systemMethods.get(sm.getSubSignature());
				if (parentMethod != null) {
					// This is a real callback method
					changed |= this.callbackMethods.put(callbackClass,
							new AndroidCallbackDefinition(sm, parentMethod, CallbackType.Widget));
				}
			}
		}
		return changed;
	}

	/**
	 * Creates the main method based on the current callback information, injects it
	 * into the Soot scene.
	 * 
	 * @param The class name of a component to create a main method containing only
	 *            that component, or null to create main method for all components
	 */
	private void createMainMethod(SootClass component) {
		// There is no need to create a main method if we don't want to generate
		// a callgraph
		if (config.getSootIntegrationMode() == SootIntegrationMode.UseExistingCallgraph)
			return;

		// Always update the entry point creator to reflect the newest set
		// of callback methods
		entryPointCreator = createEntryPointCreator(component);
		SootMethod dummyMainMethod = entryPointCreator.createDummyMain();
		Scene.v().setEntryPoints(Collections.singletonList(dummyMainMethod));
		if (!dummyMainMethod.getDeclaringClass().isInScene())
			Scene.v().addClass(dummyMainMethod.getDeclaringClass());

		// addClass() declares the given class as a library class. We need to
		// fix this.
		dummyMainMethod.getDeclaringClass().setApplicationClass();
	}

	/**
	 * Gets the source/sink manager constructed for FlowDroid. Make sure to call
	 * constructCallgraph() first, or you will get a null result.
	 * 
	 * @return FlowDroid's source/sink manager
	 */
	public ISourceSinkManager getSourceSinkManager() {
		return sourceSinkManager;
	}

	/**
	 * Builds the classpath for this analysis
	 * 
	 * @return The classpath to be used for the taint analysis
	 */
	private String getClasspath() {
		final String androidJar = config.getAnalysisFileConfig().getAndroidPlatformDir();
		final String apkFileLocation = config.getAnalysisFileConfig().getTargetAPKFile();
		final String additionalClasspath = config.getAnalysisFileConfig().getAdditionalClasspath();

		String classpath = forceAndroidJar ? androidJar : Scene.v().getAndroidJarPath(androidJar, apkFileLocation);
		if (additionalClasspath != null && !additionalClasspath.isEmpty())
			classpath += File.pathSeparator + additionalClasspath;
		logger.debug("soot classpath: " + classpath);
		return classpath;
	}

	/**
	 * Initializes soot for running the soot-based phases of the application
	 * metadata analysis
	 */
	private void initializeSoot() {
		logger.info("Initializing Soot...");

		final String androidJar = config.getAnalysisFileConfig().getAndroidPlatformDir();
		final String apkFileLocation = config.getAnalysisFileConfig().getTargetAPKFile();

		// Clean up any old Soot instance we may have
		G.reset();

		Options.v().set_no_bodies_for_excluded(true);
		Options.v().set_allow_phantom_refs(true);
		if (config.getWriteOutputFiles())
			Options.v().set_output_format(Options.output_format_jimple);
		else
			Options.v().set_output_format(Options.output_format_none);
		Options.v().set_whole_program(true);
		Options.v().set_process_dir(Collections.singletonList(apkFileLocation));
		if (forceAndroidJar)
			Options.v().set_force_android_jar(androidJar);
		else
			Options.v().set_android_jars(androidJar);
		Options.v().set_src_prec(Options.src_prec_apk_class_jimple);
		Options.v().set_keep_offset(false);
		Options.v().set_keep_line_number(config.getEnableLineNumbers());
		Options.v().set_throw_analysis(Options.throw_analysis_dalvik);
		Options.v().set_process_multiple_dex(config.getMergeDexFiles());
		Options.v().set_ignore_resolution_errors(true);

		// Set soot phase option if original names should be used
		if (config.getEnableOriginalNames())
			Options.v().setPhaseOption("jb", "use-original-names:true");

		// Set the Soot configuration options. Note that this will needs to be
		// done before we compute the classpath.
		if (sootConfig != null)
			sootConfig.setSootOptions(Options.v(), config);

		Options.v().set_soot_classpath(getClasspath());
		Main.v().autoSetOptions();
		configureCallgraph();

		// Load whatever we need
		logger.info("Loading dex files...");
		Scene.v().loadNecessaryClasses();

		// Make sure that we have valid Jimple bodies
		PackManager.v().getPack("wjpp").apply();

		// Patch the callgraph to support additional edges. We do this now,
		// because during callback discovery, the context-insensitive callgraph
		// algorithm would flood us with invalid edges.
		LibraryClassPatcher patcher = getLibraryClassPatcher();
		patcher.patchLibraries();
	}

	protected LibraryClassPatcher getLibraryClassPatcher() {
		return new LibraryClassPatcher();
	}

	/**
	 * Configures the callgraph options for Soot according to FlowDroid's settings
	 */
	protected void configureCallgraph() {
		// Configure the callgraph algorithm
		switch (config.getCallgraphAlgorithm()) {
		case AutomaticSelection:
		case SPARK:
			Options.v().setPhaseOption("cg.spark", "on");
			break;
		case GEOM:
			Options.v().setPhaseOption("cg.spark", "on");
			AbstractInfoflow.setGeomPtaSpecificOptions();
			break;
		case CHA:
			Options.v().setPhaseOption("cg.cha", "on");
			break;
		case RTA:
			Options.v().setPhaseOption("cg.spark", "on");
			Options.v().setPhaseOption("cg.spark", "rta:true");
			Options.v().setPhaseOption("cg.spark", "on-fly-cg:false");
			break;
		case VTA:
			Options.v().setPhaseOption("cg.spark", "on");
			Options.v().setPhaseOption("cg.spark", "vta:true");
			break;
		default:
			throw new RuntimeException("Invalid callgraph algorithm");
		}
		if (config.getEnableReflection())
			Options.v().setPhaseOption("cg", "types-for-invoke:true");
	}

	/**
	 * Common interface for specialized versions of the {@link Infoflow} class that
	 * allows the data flow analysis to be run inside an existing Soot instance
	 * 
	 * @author Steven Arzt
	 *
	 */
	protected static interface IInPlaceInfoflow extends IInfoflow {

		public void runAnalysis(final ISourceSinkManager sourcesSinks, SootMethod entryPoint);

	}

	/**
	 * Specialized {@link Infoflow} class that allows the data flow analysis to be
	 * run inside an existing Soot instance
	 * 
	 * @author Steven Arzt
	 *
	 */
	protected class InPlaceInfoflow extends Infoflow implements IInPlaceInfoflow {

		/**
		 * Creates a new instance of the Infoflow class for analyzing Android APK files.
		 * 
		 * @param androidPath                 If forceAndroidJar is false, this is the
		 *                                    base directory of the platform files in
		 *                                    the Android SDK. If forceAndroidJar is
		 *                                    true, this is the full path of a single
		 *                                    android.jar file.
		 * @param forceAndroidJar             True if a single platform JAR file shall
		 *                                    be forced, false if Soot shall pick the
		 *                                    appropriate platform version
		 * @param icfgFactory                 The interprocedural CFG to be used by the
		 *                                    InfoFlowProblem
		 * @param additionalEntryPointMethods Additional methods generated by the entry
		 *                                    point creator that are not directly entry
		 *                                    ypoints on their own
		 */
		public InPlaceInfoflow(String androidPath, boolean forceAndroidJar, BiDirICFGFactory icfgFactory,
				Collection additionalEntryPointMethods) {
			super(androidPath, forceAndroidJar, icfgFactory);
			this.additionalEntryPointMethods = additionalEntryPointMethods;
		}

		@Override
		public void runAnalysis(final ISourceSinkManager sourcesSinks, SootMethod entryPoint) {
			this.dummyMainMethod = entryPoint;
			super.runAnalysis(sourcesSinks);
		}

		@Override
		protected boolean isUserCodeClass(String className) {
			String packageName = manifest.getPackageName() + ".";
			return super.isUserCodeClass(className) || className.startsWith(packageName);
		}

	}

	protected class InPlaceBackwardsInfoflow extends BackwardsInfoflow implements IInPlaceInfoflow {

		/**
		 * Creates a new instance of the Infoflow class for analyzing Android APK files.
		 *
		 * @param androidPath                 If forceAndroidJar is false, this is the
		 *                                    base directory of the platform files in
		 *                                    the Android SDK. If forceAndroidJar is
		 *                                    true, this is the full path of a single
		 *                                    android.jar file.
		 * @param forceAndroidJar             True if a single platform JAR file shall
		 *                                    be forced, false if Soot shall pick the
		 *                                    appropriate platform version
		 * @param icfgFactory                 The interprocedural CFG to be used by the
		 *                                    InfoFlowProblem
		 * @param additionalEntryPointMethods Additional methods generated by the entry
		 *                                    point creator that are not directly entry
		 *                                    ypoints on their own
		 */
		public InPlaceBackwardsInfoflow(String androidPath, boolean forceAndroidJar, BiDirICFGFactory icfgFactory,
				Collection additionalEntryPointMethods) {
			super(androidPath, forceAndroidJar, icfgFactory);
			this.additionalEntryPointMethods = additionalEntryPointMethods;
		}

		@Override
		public void runAnalysis(final ISourceSinkManager sourcesSinks, SootMethod entryPoint) {
			this.dummyMainMethod = entryPoint;
			super.runAnalysis(sourcesSinks);
		}

		@Override
		protected boolean isUserCodeClass(String className) {
			String packageName = manifest.getPackageName() + ".";
			return super.isUserCodeClass(className) || className.startsWith(packageName);
		}

	}

	/**
	 * Constructs a callgraph only without running the actual data flow analysis. If
	 * you want to run a data flow analysis, do not call this method. Instead, call
	 * runInfoflow() directly, which will take care all necessary prerequisites.
	 */
	public void constructCallgraph() {
		boolean oldRunAnalysis = config.isTaintAnalysisEnabled();
		try {
			config.setTaintAnalysisEnabled(false);

			// If FlowDroid is configured to use an existing callgraph, we cannot create a
			// new one
			if (!config.getSootIntegrationMode().needsToBuildCallgraph())
				throw new RuntimeException("FlowDroid is configured to use an existing callgraph. Please "
						+ "change this option before trying to create a new callgraph.");

			// The runInfoflow method can take a null provider as long as we
			// don't attempt to run a data flow analysis.
			this.runInfoflow((ISourceSinkDefinitionProvider) null);
		} catch (RuntimeException ex) {
			logger.error("Could not construct callgraph", ex);
			throw ex;
		} finally {
			config.setTaintAnalysisEnabled(oldRunAnalysis);
		}
	}

	/**
	 * Runs the data flow analysis.
	 * 
	 * @param sources The methods that shall be considered as sources
	 * @param sinks   The methods that shall be considered as sinks
	 * @throws IOException            Thrown if the given source/sink file could not
	 *                                be read.
	 * @throws XmlPullParserException Thrown if the Android manifest file could not
	 *                                be read.
	 */
	public InfoflowResults runInfoflow(Set sources, Set sinks)
			throws IOException, XmlPullParserException {
		final Set sourceDefs = new HashSet<>(sources.size());
		final Set sinkDefs = new HashSet<>(sinks.size());

		for (AndroidMethod am : sources)
			sourceDefs.add(new MethodSourceSinkDefinition(am));
		for (AndroidMethod am : sinks)
			sinkDefs.add(new MethodSourceSinkDefinition(am));

		ISourceSinkDefinitionProvider parser = new ISourceSinkDefinitionProvider() {

			@Override
			public Set getSources() {
				return sourceDefs;
			}

			@Override
			public Set getSinks() {
				return sinkDefs;
			}

			@Override
			public Set getAllMethods() {
				Set sourcesSinks = new HashSet<>(sourceDefs.size() + sinkDefs.size());
				sourcesSinks.addAll(sourceDefs);
				sourcesSinks.addAll(sinkDefs);
				return sourcesSinks;
			}

		};

		return runInfoflow(parser);
	}

	/**
	 * Runs the data flow analysis.
	 * 
	 * @param sourceSinkFile The full path and file name of the file containing the
	 *                       sources and sinks
	 * @throws IOException            Thrown if the given source/sink file could not
	 *                                be read.
	 * @throws XmlPullParserException Thrown if the Android manifest file could not
	 *                                be read.
	 */
	public InfoflowResults runInfoflow(String sourceSinkFile) throws IOException, XmlPullParserException {
		if (sourceSinkFile != null && !sourceSinkFile.isEmpty())
			config.getAnalysisFileConfig().setSourceSinkFile(sourceSinkFile);

		return runInfoflow();
	}

	/**
	 * Runs the data flow analysis.
	 * 
	 * @throws IOException            Thrown if the given source/sink file could not
	 *                                be read.
	 * @throws XmlPullParserException Thrown if the Android manifest file could not
	 *                                be read.
	 */
	public InfoflowResults runInfoflow() throws IOException, XmlPullParserException {
		// If we don't have a source/sink file by now, we cannot run the data
		// flow analysis
		String sourceSinkFile = config.getAnalysisFileConfig().getSourceSinkFile();
		if (sourceSinkFile == null || sourceSinkFile.isEmpty())
			throw new RuntimeException("No source/sink file specified for the data flow analysis");
		String fileExtension = sourceSinkFile.substring(sourceSinkFile.lastIndexOf("."));
		fileExtension = fileExtension.toLowerCase();

		ISourceSinkDefinitionProvider parser = null;
		try {
			if (fileExtension.equals(".xml")) {
				parser = XMLSourceSinkParser.fromFile(sourceSinkFile,
						new ConfigurationBasedCategoryFilter(config.getSourceSinkConfig()));
			} else if (fileExtension.equals(".txt"))
				parser = PermissionMethodParser.fromFile(sourceSinkFile);
			else if (fileExtension.equals(".rifl"))
				parser = new RIFLSourceSinkDefinitionProvider(sourceSinkFile);
			else
				throw new UnsupportedSourceSinkFormatException("The Inputfile isn't a .txt or .xml file.");
		} catch (SAXException ex) {
			throw new IOException("Could not read XML file", ex);
		}

		return runInfoflow(parser);
	}

	/**
	 * Runs the data flow analysis.
	 * 
	 * @param sourcesAndSinks The sources and sinks of the data flow analysis
	 * @return The results of the data flow analysis
	 */
	public InfoflowResults runInfoflow(ISourceSinkDefinitionProvider sourcesAndSinks) {
		// Reset our object state
		this.collectedSources = config.getLogSourcesAndSinks() ? new HashSet() : null;
		this.collectedSinks = config.getLogSourcesAndSinks() ? new HashSet() : null;
		this.sourceSinkProvider = sourcesAndSinks;
		this.infoflow = null;

		// Perform some sanity checks on the configuration
		if (config.getSourceSinkConfig().getEnableLifecycleSources() && config.getIccConfig().isIccEnabled()) {
			logger.warn("ICC model specified, automatically disabling lifecycle sources");
			config.getSourceSinkConfig().setEnableLifecycleSources(false);
		}

		// Start a new Soot instance
		if (config.getSootIntegrationMode() == SootIntegrationMode.CreateNewInstance) {
			G.reset();
			initializeSoot();
		}

		// Perform basic app parsing
		try {
			parseAppResources();
		} catch (IOException | XmlPullParserException e) {
			logger.error("Parse app resource failed", e);
			throw new RuntimeException("Parse app resource failed", e);
		}

		MultiRunResultAggregator resultAggregator = new MultiRunResultAggregator(config.getPathAgnosticResults());

		// We need at least one entry point
		if (entrypoints == null || entrypoints.isEmpty()) {
			logger.warn("No entry points");
			return null;
		}

		// In one-component-at-a-time, we do not have a single entry point
		// creator. For every entry point, run the data flow analysis.
		if (config.getOneComponentAtATime()) {
			List entrypointWorklist = new ArrayList<>(entrypoints);
			while (!entrypointWorklist.isEmpty()) {
				SootClass entrypoint = entrypointWorklist.remove(0);
				processEntryPoint(sourcesAndSinks, resultAggregator, entrypointWorklist.size(), entrypoint);
			}
		} else
			processEntryPoint(sourcesAndSinks, resultAggregator, -1, null);

		// Write the results to disk if requested
		serializeResults(resultAggregator.getAggregatedResults(), resultAggregator.getLastICFG());

		// We return the aggregated results
		this.infoflow = null;
		resultAggregator.clearLastResults();
		return resultAggregator.getAggregatedResults();
	}

	/**
	 * Runs the data flow analysis on the given entry point class
	 * 
	 * @param sourcesAndSinks  The sources and sinks on which to run the data flow
	 *                         analysis
	 * @param resultAggregator An object for aggregating the results from the
	 *                         individual data flow runs
	 * @param numEntryPoints   The total number of runs (for logging)
	 * @param entrypoint       The current entry point to analyze
	 */
	protected void processEntryPoint(ISourceSinkDefinitionProvider sourcesAndSinks,
			MultiRunResultAggregator resultAggregator, int numEntryPoints, SootClass entrypoint) {
		long beforeEntryPoint = System.nanoTime();

		// Get rid of leftovers from the last entry point
		resultAggregator.clearLastResults();

		// Perform basic app parsing
		long callbackDuration = System.nanoTime();
		try {
			if (config.getOneComponentAtATime())
				calculateCallbacks(sourcesAndSinks, entrypoint);
			else
				calculateCallbacks(sourcesAndSinks);
		} catch (IOException | XmlPullParserException e) {
			logger.error("Callgraph construction failed: " + e.getMessage(), e);
			throw new RuntimeException("Callgraph construction failed", e);
		}
		callbackDuration = Math.round((System.nanoTime() - callbackDuration) / 1E9);
		logger.info(
				String.format("Collecting callbacks and building a callgraph took %d seconds", (int) callbackDuration));

		final Collection sources = getSources();
		final Collection sinks = getSinks();
		final String apkFileLocation = config.getAnalysisFileConfig().getTargetAPKFile();
		if (config.getOneComponentAtATime())
			logger.info("Running data flow analysis on {} (component {}/{}: {}) with {} sources and {} sinks...",
					apkFileLocation, (entrypoints.size() - numEntryPoints), entrypoints.size(), entrypoint,
					sources == null ? 0 : sources.size(), sinks == null ? 0 : sinks.size());
		else
			logger.info("Running data flow analysis on {} with {} sources and {} sinks...", apkFileLocation,
					sources == null ? 0 : sources.size(), sinks == null ? 0 : sinks.size());

		// Create a new entry point and compute the flows in it. If we
		// analyze all components together, we do not need a new callgraph,
		// but can reuse the one from the callback collection phase.
		if (config.getOneComponentAtATime() && config.getSootIntegrationMode().needsToBuildCallgraph()) {
			createMainMethod(entrypoint);
			constructCallgraphInternal();
		}

		// Create and run the data flow tracker
		infoflow = createInfoflow();
		infoflow.addResultsAvailableHandler(resultAggregator);
		infoflow.runAnalysis(sourceSinkManager, entryPointCreator.getGeneratedMainMethod());

		// Update the statistics
		if (config.getLogSourcesAndSinks() && infoflow.getCollectedSources() != null)
			this.collectedSources.addAll(infoflow.getCollectedSources());
		if (config.getLogSourcesAndSinks() && infoflow.getCollectedSinks() != null)
			this.collectedSinks.addAll(infoflow.getCollectedSinks());

		// Print out the found results
		{
			int resCount = resultAggregator.getLastResults() == null ? 0 : resultAggregator.getLastResults().size();
			if (config.getOneComponentAtATime())
				logger.info("Found {} leaks for component {}", resCount, entrypoint);
			else
				logger.info("Found {} leaks", resCount);
		}

		// Update the performance object with the real data
		{
			InfoflowResults lastResults = resultAggregator.getLastResults();
			if (lastResults != null) {
				InfoflowPerformanceData perfData = lastResults.getPerformanceData();
				if (perfData == null)
					lastResults.setPerformanceData(perfData = new InfoflowPerformanceData());
				perfData.setCallgraphConstructionSeconds((int) callbackDuration);
				perfData.setTotalRuntimeSeconds((int) Math.round((System.nanoTime() - beforeEntryPoint) / 1E9));
			}
		}

		// We don't need the computed callbacks anymore
		this.callbackMethods.clear();
		this.fragmentClasses.clear();
		this.javascriptInterfaceStmts.clear();

		// Notify our result handlers
		for (ResultsAvailableHandler handler : resultsAvailableHandlers)
			handler.onResultsAvailable(resultAggregator.getLastICFG(), resultAggregator.getLastResults());
	}

	/**
	 * Writes the given data flow results into the configured output file
	 * 
	 * @param results The data flow results to write out
	 * @param cfg     The control flow graph to use for writing out the results
	 */
	protected void serializeResults(InfoflowResults results, IInfoflowCFG cfg) {
		if (results != null && !results.isEmpty()) {
			String resultsFile = config.getAnalysisFileConfig().getOutputFile();
			if (resultsFile != null && !resultsFile.isEmpty()) {
				InfoflowResultsSerializer serializer = new InfoflowResultsSerializer(cfg, config);
				try {
					serializer.serialize(results, resultsFile);
				} catch (IOException ex) {
					System.err.println("Could not write data flow results to file: " + ex.getMessage());
					ex.printStackTrace();
				} catch (XMLStreamException ex) {
					System.err.println("Could not write data flow results to file: " + ex.getMessage());
					ex.printStackTrace();
				}
			}
		}
	}

	/**
	 * Instantiates and configures the data flow engine
	 * 
	 * @return A properly configured instance of the {@link Infoflow} class
	 */
	protected IInPlaceInfoflow createInfoflow() {
		// Some sanity checks
		if (config.getSootIntegrationMode().needsToBuildCallgraph()) {
			if (entryPointCreator == null)
				throw new RuntimeException("No entry point available");
			if (entryPointCreator.getComponentToEntryPointInfo() == null)
				throw new RuntimeException("No information about component entry points available");
		}

		// Get the component lifecycle methods
		Collection lifecycleMethods = Collections.emptySet();
		if (entryPointCreator != null) {
			ComponentEntryPointCollection entryPoints = entryPointCreator.getComponentToEntryPointInfo();
			if (entryPoints != null)
				lifecycleMethods = entryPoints.getLifecycleMethods();
		}

		// Initialize and configure the data flow tracker
		IInPlaceInfoflow info = createInfoflowInternal(lifecycleMethods);

		if (ipcManager != null)
			info.setIPCManager(ipcManager);
		info.setConfig(config);
		info.setSootConfig(sootConfig);
		info.setTaintWrapper(taintWrapper);
		info.setTaintPropagationHandler(taintPropagationHandler);
		info.setAliasPropagationHandler(aliasPropagationHandler);
		info.setUsageContextProvider(usageContextProvider);

		// We use a specialized memory manager that knows about Android
		info.setMemoryManagerFactory(new IMemoryManagerFactory() {

			@Override
			public IMemoryManager getMemoryManager(boolean tracingEnabled,
					PathDataErasureMode erasePathData) {
				return new AndroidMemoryManager(tracingEnabled, erasePathData, entrypoints);
			}

		});
		info.setMemoryManagerFactory(null);

		// Inject additional post-processors
		info.addPostProcessor(new PostAnalysisHandler() {

			@Override
			public InfoflowResults onResultsAvailable(InfoflowResults results, IInfoflowCFG cfg) {
				// Purify the ICC results if requested
				final IccConfiguration iccConfig = config.getIccConfig();
				if (iccConfig.isIccResultsPurifyEnabled()) {
					// no-op at the moment. We used to have a purifier here, but it didn't make
					// any sense. Removed it for the better.
				}

				return results;
			}

		});

		return info;
	}

	/**
	 * Creates the data flow engine on which to run the analysis. Derived classes
	 * can override this method to use other data flow engines.
	 * 
	 * @param lifecycleMethods The set of Android lifecycle methods to consider
	 * @return The data flow engine
	 */
	protected IInPlaceInfoflow createInfoflowInternal(Collection lifecycleMethods) {
		final String androidJar = config.getAnalysisFileConfig().getAndroidPlatformDir();
		if (config.getDataFlowDirection() == InfoflowConfiguration.DataFlowDirection.Backwards)
			return new InPlaceBackwardsInfoflow(androidJar, forceAndroidJar, cfgFactory, lifecycleMethods);
		return new InPlaceInfoflow(androidJar, forceAndroidJar, cfgFactory, lifecycleMethods);
	}

	/**
	 * Creates the {@link AndroidEntryPointCreator} instance which will later create
	 * the dummy main method for the analysis
	 * 
	 * @param component The single component to include in the dummy main method.
	 *                  Pass null to include all components in the dummy main
	 *                  method.
	 * @return The {@link AndroidEntryPointCreator} responsible for generating the
	 *         dummy main method
	 */
	private AndroidEntryPointCreator createEntryPointCreator(SootClass component) {
		Set components = getComponentsToAnalyze(component);

		// If we we already have an entry point creator, we make sure to clean up our
		// leftovers from previous runs
		if (entryPointCreator == null)
			entryPointCreator = createEntryPointCreator(components);
		else {
			entryPointCreator.removeGeneratedMethods(false);
			entryPointCreator.reset();
		}

		MultiMap callbackMethodSigs = new HashMultiMap<>();
		if (component == null) {
			// Get all callbacks for all components
			for (SootClass sc : this.callbackMethods.keySet()) {
				Set callbackDefs = this.callbackMethods.get(sc);
				if (callbackDefs != null)
					for (AndroidCallbackDefinition cd : callbackDefs)
						callbackMethodSigs.put(sc, cd.getTargetMethod());
			}
		} else {
			// Get the callbacks for the current component only
			for (SootClass sc : components) {
				Set callbackDefs = this.callbackMethods.get(sc);
				if (callbackDefs != null)
					for (AndroidCallbackDefinition cd : callbackDefs)
						callbackMethodSigs.put(sc, cd.getTargetMethod());
			}
		}
		entryPointCreator.setCallbackFunctions(callbackMethodSigs);
		entryPointCreator.setFragments(fragmentClasses);
		entryPointCreator.setComponents(components);
		entryPointCreator.setJavaScriptInterfaces(javascriptInterfaceStmts);
		return entryPointCreator;
	}

	/**
	 * Creates a new instance of the entry point creator
	 * 
	 * @param components The components for which the entry point creator shallk be
	 *                   responsible
	 * @return The new instance of the entry point creator
	 */
	protected AndroidEntryPointCreator createEntryPointCreator(Set components) {
		return new AndroidEntryPointCreator(manifest, components);
	}

	/**
	 * Gets the components to analyze. If the given component is not null, we assume
	 * that only this component and the application class (if any) shall be
	 * analyzed. Otherwise, all components are to be analyzed.
	 * 
	 * @param component A component class name to only analyze this class and the
	 *                  application class (if any), or null to analyze all classes.
	 * @return The set of classes to analyze
	 */
	private Set getComponentsToAnalyze(SootClass component) {
		if (component == null)
			return this.entrypoints;
		else {
			// We always analyze the application class together with each
			// component as there might be interactions between the two
			Set components = new HashSet<>(2);
			components.add(component);

			IAndroidApplication app = manifest.getApplication();
			if (app != null) {
				String applicationName = app.getName();
				if (applicationName != null && !applicationName.isEmpty())
					components.add(Scene.v().getSootClassUnsafe(applicationName));
			}
			return components;
		}
	}

	/**
	 * Gets the dummy main method that was used for the last callgraph construction.
	 * You need to run a data flow analysis or call constructCallgraph() first,
	 * otherwise you will get a null result.
	 * 
	 * @return The dummy main method
	 */
	public SootMethod getDummyMainMethod() {
		return entryPointCreator.getGeneratedMainMethod();
	}

	/**
	 * Gets the extra Soot configuration options to be used when running the
	 * analysis
	 * 
	 * @return The extra Soot configuration options to be used when running the
	 *         analysis, null if the defaults shall be used
	 */
	public IInfoflowConfig getSootConfig() {
		return this.sootConfig;
	}

	/**
	 * Sets the extra Soot configuration options to be used when running the
	 * analysis
	 * 
	 * @param config The extra Soot configuration options to be used when running
	 *               the analysis, null if the defaults shall be used
	 */
	public void setSootConfig(IInfoflowConfig config) {
		this.sootConfig = config;
	}

	/**
	 * Sets the factory class to be used for constructing interprocedural control
	 * flow graphs
	 * 
	 * @param factory The factory to be used. If null is passed, the default factory
	 *                is used.
	 */
	public void setIcfgFactory(BiDirICFGFactory factory) {
		this.cfgFactory = factory;
	}

	/**
	 * Gets the data flow configuration
	 * 
	 * @return The current data flow configuration
	 */
	public InfoflowAndroidConfiguration getConfig() {
		return this.config;
	}

	public void setCallbackFile(String callbackFile) {
		this.callbackFile = callbackFile;
	}

	/**
	 * Adds custom code to be executed before the taint propagation starts
	 * 
	 * @param preprocessor The callback to invoke before starting the taint
	 *                     propagation
	 */
	public void addPreprocessor(PreAnalysisHandler preprocessor) {
		this.preprocessors.add(preprocessor);
	}

	/**
	 * Adds a new handler that shall be executed when the results of the data flow
	 * analysis are available
	 * 
	 * @param handler The callback to invoke when the data flow results are
	 *                available
	 */
	public void addResultsAvailableHandler(ResultsAvailableHandler handler) {
		this.resultsAvailableHandlers.add(handler);
	}

	/**
	 * Clears the list of handlers that shall be executed when the data flow results
	 * are available
	 */
	public void clearResultsAvailableHandlers() {
		this.resultsAvailableHandlers.clear();
	}

	/**
	 * Aborts the data flow analysis. This is useful when the analysis controller is
	 * running in a different thread and the main thread (e.g., a GUI) wants to
	 * abort the analysis
	 */
	public void abortAnalysis() {
		if (infoflow != null)
			infoflow.abortAnalysis();
	}

	/**
	 * Sets the IPC manager for modeling inter-component and inter-application data
	 * flows
	 * 
	 * @param ipcManager The IPC manager to use for modeling inter-component and
	 *                   inter-application data flows
	 */
	public void setIpcManager(IIPCManager ipcManager) {
		this.ipcManager = ipcManager;
	}

	/**
	 * Sets the taint propagation handler, which will be notified when the data flow
	 * analysis processes an edge. Use this callback if you need a hook into the
	 * low-level data flow analysis.
	 * 
	 * @param taintPropagationHandler The propagation handler to register
	 */
	public void setTaintPropagationHandler(TaintPropagationHandler taintPropagationHandler) {
		this.taintPropagationHandler = taintPropagationHandler;
	}

	/**
	 * Sets the alias propagation handler, which will be notified when the alias
	 * analysis processes an edge. Use this callback if you need a hook into the
	 * low-level alias analysis.
	 * 
	 * @param aliasPropagationHandler The propagation handler to register
	 */
	public void setAliasPropagationHandler(TaintPropagationHandler aliasPropagationHandler) {
		this.aliasPropagationHandler = aliasPropagationHandler;
	}

	/**
	 * Sets the value provider that extracts constants from the Jimple code
	 * 
	 * @param valueProvider The value provider for extracting constants from the
	 *                      code
	 */
	public void setValueProvider(IValueProvider valueProvider) {
		this.valueProvider = valueProvider;
	}

	/**
	 * Removes all simulated code elements generated during entry point creation and
	 * ICC instrumentation from the Soot Scene
	 */
	public void removeSimulatedCodeElements() {
		for (Iterator scIt = Scene.v().getClasses().iterator(); scIt.hasNext();) {
			SootClass sc = scIt.next();
			if (sc.hasTag(SimulatedCodeElementTag.TAG_NAME))
				scIt.remove();
			else {
				for (Iterator smIt = sc.getMethods().iterator(); smIt.hasNext();) {
					SootMethod sm = smIt.next();
					if (sm.hasTag(SimulatedCodeElementTag.TAG_NAME))
						smIt.remove();
				}
				for (Iterator sfIt = sc.getFields().iterator(); sfIt.hasNext();) {
					SootField sf = sfIt.next();
					if (sf.hasTag(SimulatedCodeElementTag.TAG_NAME))
						sfIt.remove();
				}
			}
		}
	}

	public void setUsageContextProvider(IUsageContextProvider usageContextProvider) {
		this.usageContextProvider = usageContextProvider;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy