<commit_before>/**
 * Sapelli data collection platform: http://sapelli.org
 * 
 * Copyright 2012-2014 University College London - ExCiteS group
 * 
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */

package uk.ac.ucl.excites.sapelli.collector.activities;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import uk.ac.ucl.excites.sapelli.collector.BuildConfig;
import uk.ac.ucl.excites.sapelli.collector.CollectorApp;
import uk.ac.ucl.excites.sapelli.collector.R;
import uk.ac.ucl.excites.sapelli.collector.SapelliCollectorClient;
import uk.ac.ucl.excites.sapelli.collector.db.ProjectStore;
import uk.ac.ucl.excites.sapelli.collector.io.ProjectLoader;
import uk.ac.ucl.excites.sapelli.collector.io.ProjectLoaderClient;
import uk.ac.ucl.excites.sapelli.collector.model.Project;
import uk.ac.ucl.excites.sapelli.collector.util.AsyncZipper;
import uk.ac.ucl.excites.sapelli.collector.util.DeviceID;
import uk.ac.ucl.excites.sapelli.collector.util.DuplicateException;
import uk.ac.ucl.excites.sapelli.collector.util.ProjectRunHelpers;
import uk.ac.ucl.excites.sapelli.collector.util.qrcode.IntentIntegrator;
import uk.ac.ucl.excites.sapelli.collector.util.qrcode.IntentResult;
import uk.ac.ucl.excites.sapelli.collector.xml.ProjectParser;
import uk.ac.ucl.excites.sapelli.shared.db.StoreClient;
import uk.ac.ucl.excites.sapelli.shared.io.FileHelpers;
import uk.ac.ucl.excites.sapelli.shared.util.ExceptionHelpers;
import uk.ac.ucl.excites.sapelli.shared.util.StringUtils;
import uk.ac.ucl.excites.sapelli.shared.util.TimeUtils;
import uk.ac.ucl.excites.sapelli.storage.eximport.xml.XMLRecordsImporter;
import uk.ac.ucl.excites.sapelli.storage.model.Record;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.AssetManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.util.Patterns;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import com.crashlytics.android.Crashlytics;
import com.ipaulpro.afilechooser.utils.FileUtils;
import com.larvalabs.svgandroid.SVG;
import com.larvalabs.svgandroid.SVGBuilder;
import com.larvalabs.svgandroid.SVGDrawable;

/**
 * @author Julia, <NAME>, mstevens
 * 
 */
public class ProjectManagerActivity extends BaseActivity implements ProjectLoaderClient, StoreClient, DeviceID.InitialisationCallback
{

	// STATICS--------------------------------------------------------
	static private final String TAG = "ProjectManagerActivity";
	
	static private final String XML_FILE_EXTENSION = "xml";

	static private final String DEMO_PROJECT = "demo.excites";

	public static final int RETURN_BROWSE_FOR_PROJECT_LOAD = 1;
	public static final int RETURN_BROWSE_FOR_RECORD_IMPORT = 2;

	// DYNAMICS-------------------------------------------------------
	private ProjectStore projectStore;

	// UI
	private EditText enterURL;
	private ListView projectList;
	private Button runBtn;
	private Button removeBtn;
	private Dialog encryptionDialog;
	private DeviceID deviceID;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		
		// Check if we can access read/write to the Sapelli folder (created on the SD card or internal mass storage if there is no physical SD card):
		try
		{
			app.getSapelliFolder(); //throws IllegalStateException if not accessible or not create-able
		}
		catch(IllegalStateException ise)
		{	// Inform the user and close the application
			showErrorDialog(getString(R.string.app_name) + " " + getString(R.string.needsStorageAccess), true);
			return;
		}
		
		if(app.getBuildInfo().isDemoBuild())
			return;
		//else ...
		// Only if not in demo mode:
		
		// Set-up UI...
		setTitle(getString(R.string.app_name) + ' ' + getString(R.string.project_manager));
		// Hide soft keyboard on create
		getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
		setContentView(R.layout.activity_projectmanager);
		// Get View Elements
		enterURL = (EditText) findViewById(R.id.EnterURL);
		projectList = (ListView) findViewById(R.id.ProjectsList);
		runBtn = (Button) findViewById(R.id.RunProjectButton);
		removeBtn = (Button) findViewById(R.id.RemoveProjectButton);
		// Set background logo under project list:
		if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT /* = 19 */)
			try	// For some reason the background image blows up the size of the containing RelativeLayout on Android < KitKat. TODO investigate further & ask on StackOverflow and/or contact AndroidSVG creator
			{
				ImageView projListBackground = (ImageView) findViewById(R.id.ProjectsListBackgroundImage);
				SVG svg = new SVGBuilder().readFromResource(getResources(), R.drawable.sapelli_logo).build();
				projListBackground.setImageDrawable(new SVGDrawable(svg));
				//SVG svg = SVG.getFromResource(getResources(), R.drawable.sapelli_logo);
				//projListBackground.setImageDrawable(new PictureDrawable(svg.renderToPicture()));
			}
			catch(Exception e)
			{
				e.printStackTrace(System.err);
			}
		// Make title bar click open the about dialog:
		View v = findViewById (android.R.id.title);
	    v.setClickable(true);
	    v.setOnClickListener(new OnClickListener()
	    {
	        @Override
	        public void onClick(View v)
	        {
	        	openAboutDialog(null);
	        }
	    });
		// Get scrolling right
		findViewById(R.id.projectManager_ScrollView).setOnTouchListener(new View.OnTouchListener()
		{
			@Override
			public boolean onTouch(View v, MotionEvent event)
			{
				projectList.getParent().requestDisallowInterceptTouchEvent(false);
				return false;
			}
		});
		projectList.setOnTouchListener(new View.OnTouchListener()
		{
			public boolean onTouch(View v, MotionEvent event)
			{	// Disallow the touch request for parent scroll on touch of child view
				v.getParent().requestDisallowInterceptTouchEvent(true);
				return false;
			}
		});

/*		// TODO Re-enable the service at same point
		// Check the Preferences
		if(DataSenderPreferences.getTimeSchedule(this) == 1)
		{
			DataSenderPreferences.printPreferences(this);
			Toast.makeText(this, "Please configure the Data Sender.", Toast.LENGTH_LONG).show();
			
			Intent settingsActivity = new Intent(this, DataSenderPreferences.class);
			startActivity(settingsActivity);
		}
		
		// Start the DataSenderService
		if(DataSenderPreferences.getSenderEnabled(this)) //TODO make this optional
			ServiceChecker.startService(this);
*/
	}

	@Override
	protected void onResume()
	{
		super.onResume();
		
		// Initialise DeviceID:
		DeviceID.Initialise(this, this); // will post a callback upon completion (success/failure)

		// Get ProjectStore instance:
		try
		{
			projectStore = app.getProjectStore(this);
		}
		catch(Exception e)
		{
			showErrorDialog(getString(R.string.projectStorageAccessFail, ExceptionHelpers.getMessageAndCause(e)), true);
			return;
		}
		
		// And finally...
		if(app.getBuildInfo().isDemoBuild())
			demoMode();
		else
			populateProjectList(); 	// Update project list
		// TODO remember & re-select last selected project
	}
	
	@Override
	public void initialisationSuccess(DeviceID deviceID)
	{
		this.deviceID = deviceID;
		if(!BuildConfig.DEBUG)
		{
			Crashlytics.setLong(CollectorApp.CRASHLYTICS_DEVICE_ID_CRC32, deviceID.getIDAsCRC32Hash());
			Crashlytics.setString(CollectorApp.CRASHLYTICS_DEVICE_ID_MD5, deviceID.getIDAsMD5Hash().toString());
		}
	}

	@Override
	public void initialisationFailure(DeviceID deviceID)
	{
		deviceID.printInfo();
		showErrorDialog(R.string.noDeviceID, true);
	}

	private void demoMode()
	{
		try
		{
			List<Project> projects = projectStore.retrieveProjects();
			Project p = null;
			if(projects.isEmpty())
			{	// Use /mnt/sdcard/Sapelli/ as the basePath:
				ProjectLoader loader = new ProjectLoader(this, app.getProjectFolderPath(), app.getTempFolderPath());
				p = loader.load(this.getAssets().open(DEMO_PROJECT, AssetManager.ACCESS_RANDOM));
				storeProject(p);
			}
			else
				p = projects.get(0); // Assumption: there is only one stored project in demo mode
			// Run the project
			startActivity(ProjectRunHelpers.getProjectRunIntent(this, p));
		}
		catch(Exception e)
		{
			Log.e(TAG, "Error loading/storing/launching demo project", e);
			showErrorDialog(R.string.demoLoadFail, true);
		}
	}

	@Override
	protected void onDestroy()
	{
		// clean up:
		app.discardStoreUsage(projectStore, this); // signal that the activity no longer needs the DAO
		// super:
		super.onDestroy();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu)
	{
		super.onCreateOptionsMenu(menu);
		getMenuInflater().inflate(R.menu.projectmanager, menu);
		return true;
	}
	
	@Override
	public boolean onOptionsItemSelected(MenuItem item)
	{
		/* 	Note:
		 * 	the android:onClick attribute in the XML only works on Android >= v3.0,
		 *	so we need to direct the handling of menu clicks manually here for things
		 *	to work on earlier versions. */
	    switch(item.getItemId())
	    {
	    	case R.id.sender_settings_menuitem :
	    		return openSenderSettings(item);
	    	case R.id.export_records_menuitem :
	    		return exportRecords(item);
	    	//case R.id.import_records_menuitem :
	    		//return importRecords(item);
	    	case R.id.create_shortcut :
	    		return createShortcut(item);
	    	case R.id.remove_shortcut :
	    		return removeShortcut(item);
	    	case R.id.copy_db_menuitem :
	    		return copyDBtoSD(item);
			case R.id.zip_files:
				return zipSapelliFiles(item);
	    	case R.id.about_menuitem :
	    		return openAboutDialog(item);
	    }
	    return true;
	}

	public boolean openSenderSettings(MenuItem item)
	{
		// TODO Re-enable the service at same point
		// startActivity(new Intent(getBaseContext(), DataSenderPreferences.class));
		return true;
	}

	@SuppressLint("InflateParams")
	public boolean openAboutDialog(MenuItem item)
	{
		// Set-up UI:
		View view = LayoutInflater.from(this).inflate(R.layout.dialog_about, null);
		TextView infoLbl = (TextView) view.findViewById(R.id.aboutInfo);
		infoLbl.setClickable(true);
		infoLbl.setMovementMethod(LinkMovementMethod.getInstance());
		infoLbl.setText(Html.fromHtml(
				"<p><b>" + app.getBuildInfo().getVersionInfo() + "</b></p>" +
				"<p>" + app.getBuildInfo().getBuildInfo() + ".</p>" +
				"<p>" + getString(R.string.by_ucl_excites_html)  + "</p>" + 
				"<p>" + getString(R.string.license)  + "</p>" +
				"<p>" + "Device ID (CRC32): " + (deviceID != null ? deviceID.getIDAsCRC32Hash() : "?") + ".</p>"));	
		infoLbl.setPadding(2, 2, 6, 2);
		ImageView iconImg = (ImageView) view.findViewById(R.id.aboutIcon);
		iconImg.setOnClickListener(new OnClickListener()
		{
			@Override
			public void onClick(View v)
			{
				startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://sapelli.org")));
			}
		});
		
		// Set-up dialog:
		AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
		dialogBuilder.setPositiveButton(getString(android.R.string.ok), null); // click will dismiss the dialog (BACK press will too)
		AlertDialog aboutDialog = dialogBuilder.create();
		aboutDialog.setView(view, 0, 0, 0, 0); // no margins

		// Show the dialog:
		aboutDialog.show();
		
		return true;
	}
	
	public boolean exportRecords(MenuItem item)
	{
		Project selectedProject = getSelectedProject(false);
		Intent i = new Intent(getApplicationContext(), ExportActivity.class);
		if(selectedProject != null)
		{
			i.putExtra(CollectorActivity.INTENT_PARAM_PROJECT_ID, selectedProject.getID());
			i.putExtra(CollectorActivity.INTENT_PARAM_PROJECT_FINGERPRINT, selectedProject.getFingerPrint());
		}
		i.setAction(Intent.ACTION_MAIN);
		startActivity(i);
		return true;
	}

	public boolean importRecords(MenuItem item)
	{
		// Use the GET_CONTENT intent from the utility class
		Intent target = FileUtils.createGetContentIntent();
		// Create the chooser Intent
		Intent intent = Intent.createChooser(target, "Choose an XML file");
		try
		{
			startActivityForResult(intent, RETURN_BROWSE_FOR_RECORD_IMPORT);
		}
		catch(ActivityNotFoundException e){}

		return true;
	}

	public boolean copyDBtoSD(MenuItem item)
	{
		try
		{
			app.backupStores();
		}
		catch(Exception e)
		{
			showErrorDialog(getString(R.string.backupFailDueTo, ExceptionHelpers.getMessageAndCause(e)), false);
		}
		return true;
	}
	
	// TODO Move to FileStorageProvider?
	public static enum SapelliFolders
	{
		Dumps, Data, Logs, Projects
	}

	public boolean zipSapelliFiles(MenuItem item)
	{
		// Create the items
		final ArrayList<String> selectedItems = new ArrayList<String>(); // Where we track the selected items
		CharSequence[] folderList = new CharSequence[SapelliFolders.values().length];
		boolean[] checkedFolders = new boolean[SapelliFolders.values().length];

		for(int i = 0; i < SapelliFolders.values().length; i++)
		{
			final SapelliFolders folder = SapelliFolders.values()[i];

			switch(folder)
			{
			case Dumps:
				folderList[i] = folder.name();
				checkedFolders[i] = true;
				selectedItems.add(folder.name());
				break;

			case Data:
				folderList[i] = folder.name();
				checkedFolders[i] = false;
				break;

			case Logs:
				folderList[i] = folder.name();
				checkedFolders[i] = true;
				selectedItems.add(folder.name());
				break;

			case Projects:
				folderList[i] = folder.name();
				checkedFolders[i] = false;
				break;

			default:
				break;
			}

		}

		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		// Set the dialog title
		builder.setTitle("Select folders to export:")
		// Specify the list array, the items to be selected by default (null for none),
		// and the listener through which to receive callbacks when items are selected
				.setMultiChoiceItems(folderList, checkedFolders, new DialogInterface.OnMultiChoiceClickListener()
				{
					@Override
					public void onClick(DialogInterface dialog, int which, boolean isChecked)
					{
						if(isChecked)
						{
							// If the user checked the item, add it to the selected items
							selectedItems.add(SapelliFolders.values()[which].name());
						}
						else if(selectedItems.contains(which))
						{
							// Else, if the item is already in the array, remove it
							selectedItems.remove(Integer.valueOf(which));
						}
					}
				})
				// Set the action buttons
				.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener()
				{
					@Override
					public void onClick(DialogInterface dialog, int id)
					{
						// TODO Improve File name
						final String zipfile = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath()
								+ File.separator + "Sapelli_" + TimeUtils.getTimestampForFileName();

						// TODO Get actual file paths from FileStorageProvider
						List<String> paths = new ArrayList<String>();
						for(String f : selectedItems)
						{
							// Get the Sapelli Folder
							SapelliFolders folder = SapelliFolders.valueOf(f);

							switch(folder)
							{
							case Dumps:
								paths.add("/mnt/sdcard/Android/data/uk.ac.ucl.excites.sapelli.collector/files/Dumps");
								break;

							case Data:
								paths.add("/mnt/sdcard/Android/data/uk.ac.ucl.excites.sapelli.collector/files/Data");
								break;

							case Logs:
								paths.add("/mnt/sdcard/Android/data/uk.ac.ucl.excites.sapelli.collector/files/Logs");
								break;

							case Projects:
								paths.add("/mnt/sdcard/Android/data/uk.ac.ucl.excites.sapelli.collector/files/Projects");
								break;

							default:
								break;
							}
						}

						// Call an AsyncZipper
						AsyncZipper zipper = new AsyncZipper(ProjectManagerActivity.this, getString(R.string.exporting_data), paths, zipfile);
						zipper.execute();

					}
				}).setNegativeButton(R.string.no, new DialogInterface.OnClickListener()<commit_msg>Zip files only if there are selected folders<commit_after>/**
 * Sapelli data collection platform: http://sapelli.org
 * 
 * Copyright 2012-2014 University College London - ExCiteS group
 * 
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */

package uk.ac.ucl.excites.sapelli.collector.activities;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import uk.ac.ucl.excites.sapelli.collector.BuildConfig;
import uk.ac.ucl.excites.sapelli.collector.CollectorApp;
import uk.ac.ucl.excites.sapelli.collector.R;
import uk.ac.ucl.excites.sapelli.collector.SapelliCollectorClient;
import uk.ac.ucl.excites.sapelli.collector.db.ProjectStore;
import uk.ac.ucl.excites.sapelli.collector.io.ProjectLoader;
import uk.ac.ucl.excites.sapelli.collector.io.ProjectLoaderClient;
import uk.ac.ucl.excites.sapelli.collector.model.Project;
import uk.ac.ucl.excites.sapelli.collector.util.AsyncZipper;
import uk.ac.ucl.excites.sapelli.collector.util.DeviceID;
import uk.ac.ucl.excites.sapelli.collector.util.DuplicateException;
import uk.ac.ucl.excites.sapelli.collector.util.ProjectRunHelpers;
import uk.ac.ucl.excites.sapelli.collector.util.qrcode.IntentIntegrator;
import uk.ac.ucl.excites.sapelli.collector.util.qrcode.IntentResult;
import uk.ac.ucl.excites.sapelli.collector.xml.ProjectParser;
import uk.ac.ucl.excites.sapelli.shared.db.StoreClient;
import uk.ac.ucl.excites.sapelli.shared.io.FileHelpers;
import uk.ac.ucl.excites.sapelli.shared.util.ExceptionHelpers;
import uk.ac.ucl.excites.sapelli.shared.util.StringUtils;
import uk.ac.ucl.excites.sapelli.shared.util.TimeUtils;
import uk.ac.ucl.excites.sapelli.storage.eximport.xml.XMLRecordsImporter;
import uk.ac.ucl.excites.sapelli.storage.model.Record;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.AssetManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.util.Patterns;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.crashlytics.android.Crashlytics;
import com.ipaulpro.afilechooser.utils.FileUtils;
import com.larvalabs.svgandroid.SVG;
import com.larvalabs.svgandroid.SVGBuilder;
import com.larvalabs.svgandroid.SVGDrawable;

/**
 * @author Julia, <NAME>, mstevens
 * 
 */
public class ProjectManagerActivity extends BaseActivity implements ProjectLoaderClient, StoreClient, DeviceID.InitialisationCallback
{

	// STATICS--------------------------------------------------------
	static private final String TAG = "ProjectManagerActivity";
	
	static private final String XML_FILE_EXTENSION = "xml";

	static private final String DEMO_PROJECT = "demo.excites";

	public static final int RETURN_BROWSE_FOR_PROJECT_LOAD = 1;
	public static final int RETURN_BROWSE_FOR_RECORD_IMPORT = 2;

	// DYNAMICS-------------------------------------------------------
	private ProjectStore projectStore;

	// UI
	private EditText enterURL;
	private ListView projectList;
	private Button runBtn;
	private Button removeBtn;
	private Dialog encryptionDialog;
	private DeviceID deviceID;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		
		// Check if we can access read/write to the Sapelli folder (created on the SD card or internal mass storage if there is no physical SD card):
		try
		{
			app.getSapelliFolder(); //throws IllegalStateException if not accessible or not create-able
		}
		catch(IllegalStateException ise)
		{	// Inform the user and close the application
			showErrorDialog(getString(R.string.app_name) + " " + getString(R.string.needsStorageAccess), true);
			return;
		}
		
		if(app.getBuildInfo().isDemoBuild())
			return;
		//else ...
		// Only if not in demo mode:
		
		// Set-up UI...
		setTitle(getString(R.string.app_name) + ' ' + getString(R.string.project_manager));
		// Hide soft keyboard on create
		getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
		setContentView(R.layout.activity_projectmanager);
		// Get View Elements
		enterURL = (EditText) findViewById(R.id.EnterURL);
		projectList = (ListView) findViewById(R.id.ProjectsList);
		runBtn = (Button) findViewById(R.id.RunProjectButton);
		removeBtn = (Button) findViewById(R.id.RemoveProjectButton);
		// Set background logo under project list:
		if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT /* = 19 */)
			try	// For some reason the background image blows up the size of the containing RelativeLayout on Android < KitKat. TODO investigate further & ask on StackOverflow and/or contact AndroidSVG creator
			{
				ImageView projListBackground = (ImageView) findViewById(R.id.ProjectsListBackgroundImage);
				SVG svg = new SVGBuilder().readFromResource(getResources(), R.drawable.sapelli_logo).build();
				projListBackground.setImageDrawable(new SVGDrawable(svg));
				//SVG svg = SVG.getFromResource(getResources(), R.drawable.sapelli_logo);
				//projListBackground.setImageDrawable(new PictureDrawable(svg.renderToPicture()));
			}
			catch(Exception e)
			{
				e.printStackTrace(System.err);
			}
		// Make title bar click open the about dialog:
		View v = findViewById (android.R.id.title);
	    v.setClickable(true);
	    v.setOnClickListener(new OnClickListener()
	    {
	        @Override
	        public void onClick(View v)
	        {
	        	openAboutDialog(null);
	        }
	    });
		// Get scrolling right
		findViewById(R.id.projectManager_ScrollView).setOnTouchListener(new View.OnTouchListener()
		{
			@Override
			public boolean onTouch(View v, MotionEvent event)
			{
				projectList.getParent().requestDisallowInterceptTouchEvent(false);
				return false;
			}
		});
		projectList.setOnTouchListener(new View.OnTouchListener()
		{
			public boolean onTouch(View v, MotionEvent event)
			{	// Disallow the touch request for parent scroll on touch of child view
				v.getParent().requestDisallowInterceptTouchEvent(true);
				return false;
			}
		});

/*		// TODO Re-enable the service at same point
		// Check the Preferences
		if(DataSenderPreferences.getTimeSchedule(this) == 1)
		{
			DataSenderPreferences.printPreferences(this);
			Toast.makeText(this, "Please configure the Data Sender.", Toast.LENGTH_LONG).show();
			
			Intent settingsActivity = new Intent(this, DataSenderPreferences.class);
			startActivity(settingsActivity);
		}
		
		// Start the DataSenderService
		if(DataSenderPreferences.getSenderEnabled(this)) //TODO make this optional
			ServiceChecker.startService(this);
*/
	}

	@Override
	protected void onResume()
	{
		super.onResume();
		
		// Initialise DeviceID:
		DeviceID.Initialise(this, this); // will post a callback upon completion (success/failure)

		// Get ProjectStore instance:
		try
		{
			projectStore = app.getProjectStore(this);
		}
		catch(Exception e)
		{
			showErrorDialog(getString(R.string.projectStorageAccessFail, ExceptionHelpers.getMessageAndCause(e)), true);
			return;
		}
		
		// And finally...
		if(app.getBuildInfo().isDemoBuild())
			demoMode();
		else
			populateProjectList(); 	// Update project list
		// TODO remember & re-select last selected project
	}
	
	@Override
	public void initialisationSuccess(DeviceID deviceID)
	{
		this.deviceID = deviceID;
		if(!BuildConfig.DEBUG)
		{
			Crashlytics.setLong(CollectorApp.CRASHLYTICS_DEVICE_ID_CRC32, deviceID.getIDAsCRC32Hash());
			Crashlytics.setString(CollectorApp.CRASHLYTICS_DEVICE_ID_MD5, deviceID.getIDAsMD5Hash().toString());
		}
	}

	@Override
	public void initialisationFailure(DeviceID deviceID)
	{
		deviceID.printInfo();
		showErrorDialog(R.string.noDeviceID, true);
	}

	private void demoMode()
	{
		try
		{
			List<Project> projects = projectStore.retrieveProjects();
			Project p = null;
			if(projects.isEmpty())
			{	// Use /mnt/sdcard/Sapelli/ as the basePath:
				ProjectLoader loader = new ProjectLoader(this, app.getProjectFolderPath(), app.getTempFolderPath());
				p = loader.load(this.getAssets().open(DEMO_PROJECT, AssetManager.ACCESS_RANDOM));
				storeProject(p);
			}
			else
				p = projects.get(0); // Assumption: there is only one stored project in demo mode
			// Run the project
			startActivity(ProjectRunHelpers.getProjectRunIntent(this, p));
		}
		catch(Exception e)
		{
			Log.e(TAG, "Error loading/storing/launching demo project", e);
			showErrorDialog(R.string.demoLoadFail, true);
		}
	}

	@Override
	protected void onDestroy()
	{
		// clean up:
		app.discardStoreUsage(projectStore, this); // signal that the activity no longer needs the DAO
		// super:
		super.onDestroy();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu)
	{
		super.onCreateOptionsMenu(menu);
		getMenuInflater().inflate(R.menu.projectmanager, menu);
		return true;
	}
	
	@Override
	public boolean onOptionsItemSelected(MenuItem item)
	{
		/* 	Note:
		 * 	the android:onClick attribute in the XML only works on Android >= v3.0,
		 *	so we need to direct the handling of menu clicks manually here for things
		 *	to work on earlier versions. */
	    switch(item.getItemId())
	    {
	    	case R.id.sender_settings_menuitem :
	    		return openSenderSettings(item);
	    	case R.id.export_records_menuitem :
	    		return exportRecords(item);
	    	//case R.id.import_records_menuitem :
	    		//return importRecords(item);
	    	case R.id.create_shortcut :
	    		return createShortcut(item);
	    	case R.id.remove_shortcut :
	    		return removeShortcut(item);
	    	case R.id.copy_db_menuitem :
	    		return copyDBtoSD(item);
			case R.id.zip_files:
				return zipSapelliFiles(item);
	    	case R.id.about_menuitem :
	    		return openAboutDialog(item);
	    }
	    return true;
	}

	public boolean openSenderSettings(MenuItem item)
	{
		// TODO Re-enable the service at same point
		// startActivity(new Intent(getBaseContext(), DataSenderPreferences.class));
		return true;
	}

	@SuppressLint("InflateParams")
	public boolean openAboutDialog(MenuItem item)
	{
		// Set-up UI:
		View view = LayoutInflater.from(this).inflate(R.layout.dialog_about, null);
		TextView infoLbl = (TextView) view.findViewById(R.id.aboutInfo);
		infoLbl.setClickable(true);
		infoLbl.setMovementMethod(LinkMovementMethod.getInstance());
		infoLbl.setText(Html.fromHtml(
				"<p><b>" + app.getBuildInfo().getVersionInfo() + "</b></p>" +
				"<p>" + app.getBuildInfo().getBuildInfo() + ".</p>" +
				"<p>" + getString(R.string.by_ucl_excites_html)  + "</p>" + 
				"<p>" + getString(R.string.license)  + "</p>" +
				"<p>" + "Device ID (CRC32): " + (deviceID != null ? deviceID.getIDAsCRC32Hash() : "?") + ".</p>"));	
		infoLbl.setPadding(2, 2, 6, 2);
		ImageView iconImg = (ImageView) view.findViewById(R.id.aboutIcon);
		iconImg.setOnClickListener(new OnClickListener()
		{
			@Override
			public void onClick(View v)
			{
				startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://sapelli.org")));
			}
		});
		
		// Set-up dialog:
		AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
		dialogBuilder.setPositiveButton(getString(android.R.string.ok), null); // click will dismiss the dialog (BACK press will too)
		AlertDialog aboutDialog = dialogBuilder.create();
		aboutDialog.setView(view, 0, 0, 0, 0); // no margins

		// Show the dialog:
		aboutDialog.show();
		
		return true;
	}
	
	public boolean exportRecords(MenuItem item)
	{
		Project selectedProject = getSelectedProject(false);
		Intent i = new Intent(getApplicationContext(), ExportActivity.class);
		if(selectedProject != null)
		{
			i.putExtra(CollectorActivity.INTENT_PARAM_PROJECT_ID, selectedProject.getID());
			i.putExtra(CollectorActivity.INTENT_PARAM_PROJECT_FINGERPRINT, selectedProject.getFingerPrint());
		}
		i.setAction(Intent.ACTION_MAIN);
		startActivity(i);
		return true;
	}

	public boolean importRecords(MenuItem item)
	{
		// Use the GET_CONTENT intent from the utility class
		Intent target = FileUtils.createGetContentIntent();
		// Create the chooser Intent
		Intent intent = Intent.createChooser(target, "Choose an XML file");
		try
		{
			startActivityForResult(intent, RETURN_BROWSE_FOR_RECORD_IMPORT);
		}
		catch(ActivityNotFoundException e){}

		return true;
	}

	public boolean copyDBtoSD(MenuItem item)
	{
		try
		{
			app.backupStores();
		}
		catch(Exception e)
		{
			showErrorDialog(getString(R.string.backupFailDueTo, ExceptionHelpers.getMessageAndCause(e)), false);
		}
		return true;
	}
	
	// TODO Move to FileStorageProvider?
	public static enum SapelliFolders
	{
		Dumps, Data, Logs, Projects
	}

	public boolean zipSapelliFiles(MenuItem item)
	{
		// Create the items
		final ArrayList<String> selectedItems = new ArrayList<String>(); // Where we track the selected items
		CharSequence[] folderList = new CharSequence[SapelliFolders.values().length];
		boolean[] checkedFolders = new boolean[SapelliFolders.values().length];

		for(int i = 0; i < SapelliFolders.values().length; i++)
		{
			final SapelliFolders folder = SapelliFolders.values()[i];

			switch(folder)
			{
			case Dumps:
				folderList[i] = folder.name();
				checkedFolders[i] = true;
				selectedItems.add(folder.name());
				break;

			case Data:
				folderList[i] = folder.name();
				checkedFolders[i] = false;
				break;

			case Logs:
				folderList[i] = folder.name();
				checkedFolders[i] = true;
				selectedItems.add(folder.name());
				break;

			case Projects:
				folderList[i] = folder.name();
				checkedFolders[i] = false;
				break;

			default:
				break;
			}

		}

		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		// Set the dialog title
		builder.setTitle("Select folders to export:")
		// Specify the list array, the items to be selected by default (null for none),
		// and the listener through which to receive callbacks when items are selected
				.setMultiChoiceItems(folderList, checkedFolders, new DialogInterface.OnMultiChoiceClickListener()
				{
					@Override
					public void onClick(DialogInterface dialog, int which, boolean isChecked)
					{
						if(isChecked)
						{
							// If the user checked the item, add it to the selected items
							selectedItems.add(SapelliFolders.values()[which].name());
						}
						else if(selectedItems.contains(SapelliFolders.values()[which].name()))
						{
							// Else, if the item is already in the array, remove it
							selectedItems.remove(Integer.valueOf(which));
						}
					}
				})
				// Set the action buttons
				.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener()
				{
					@Override
					public void onClick(DialogInterface dialog, int id)
					{
						// TODO Improve File name
						final String zipfile = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath()
								+ File.separator + "Sapelli_" + TimeUtils.getTimestampForFileName();

						// TODO Get actual file paths from FileStorageProvider
						List<String> paths = new ArrayList<String>();
						for(String f : selectedItems)
						{
							// Get the Sapelli Folder
							SapelliFolders folder = SapelliFolders.valueOf(f);

							switch(folder)
							{
							case Dumps:
								paths.add("/mnt/sdcard/Android/data/uk.ac.ucl.excites.sapelli.collector/files/Dumps");
								break;

							case Data:
								paths.add("/mnt/sdcard/Android/data/uk.ac.ucl.excites.sapelli.collector/files/Data");
								break;

							case Logs:
								paths.add("/mnt/sdcard/Android/data/uk.ac.ucl.excites.sapelli.collector/files/Logs");
								break;

							case Projects:
								paths.add("/mnt/sdcard/Android/data/uk.ac.ucl.excites.sapelli.collector/files/Projects");
								break;

							default:
								break;
							}
						}

						// Call an AsyncZipper only if there are selected items
						if(!paths.isEmpty())
						{
							AsyncZipper zipper = new AsyncZipper(ProjectManagerActivity.this, getString(R.string.exporting_data), paths, zipfile);
							zipper.execute();
						}
						else
							Toast.makeText(ProjectManagerActivity.this, "You must select at least one folder to export", Toast.LENGTH_LONG).show();
					}
				}).setNegativeButton(R.string.no, new DialogInterface.OnClickListener()
{'fraction_non_alphanumeric': 0.1156600382300077, 'fraction_numerical': 0.002225328806596103, 'mean_word_length': 3.849474266740454, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '264490', 'n_tokens_mistral': 12444, 'n_tokens_neox': 10757, 'n_words': 2375}