/* This file is part of SlumDroid <https://github.com/slumdroid/slumdroid>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License <http://www.gnu.org/licenses/gpl-3.0.txt>
* for more details.
*
* Copyright (C) 2012-2016 Gennaro Imparato
*/
package it.slumdroid.tool.components.persistence;
import static it.slumdroid.tool.Resources.ACTIVITY_LIST_FILE_NAME;
import static it.slumdroid.tool.Resources.GUI_TREE_FILE_NAME;
import static it.slumdroid.tool.Resources.PARAMETERS_FILE_NAME;
import static it.slumdroid.tool.Resources.TASK_LIST_FILE_NAME;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import android.app.Activity;
import android.content.ContextWrapper;
import android.graphics.Bitmap;
import android.view.View;
import it.slumdroid.droidmodels.guitree.FinalActivity;
import it.slumdroid.droidmodels.model.ActivityState;
import it.slumdroid.droidmodels.model.Session;
import it.slumdroid.droidmodels.model.Task;
import it.slumdroid.droidmodels.xml.ElementWrapper;
import it.slumdroid.droidmodels.xml.XmlGraph;
import it.slumdroid.tool.components.automation.Automation;
import it.slumdroid.tool.model.DispatchListener;
import it.slumdroid.tool.model.SaveStateListener;
import it.slumdroid.tool.model.StateDiscoveryListener;
import it.slumdroid.tool.utilities.SessionParams;
// TODO: Auto-generated Javadoc
/**
* The Class ResumingPersistence.
*/
public class ResumingPersistence implements SaveStateListener, DispatchListener, StateDiscoveryListener {
/** The actor name. */
public final String ACTOR_NAME = "ResumingPersistence";
/** The param name. */
public final String PARAM_NAME = "resumer";
/** The xml body begin. */
private final String XML_BODY_BEGIN = " <TASK";
/** The xml body end. */
private final String XML_BODY_END = "/TASK>";
/** The task file. */
private FileOutputStream taskFile;
/** The task list. */
private List<Task> taskList;
/** The task stream. */
private OutputStreamWriter taskStream;
/** The state file. */
private FileOutputStream stateFile;
/** The state stream. */
private OutputStreamWriter stateStream;
/** The parameters. */
private Map<String, SessionParams> parameters = new Hashtable<String, SessionParams>();
/** The listeners. */
private Hashtable<String,SaveStateListener> theListeners = new Hashtable<String,SaveStateListener>();
/** The footer. */
private String footer = new String();
/** The first. */
private boolean first = true;
/** The last. */
private boolean last = false;
/** The out. */
FileOutputStream fOut = null;
/** The osw. */
OutputStreamWriter osw = null;
/** The wrapper. */
ContextWrapper wrapper = null;
/** The session. */
private Session theSession;
/** The mode. */
protected int mode = ContextWrapper.MODE_PRIVATE;
/**
* Instantiates a new resuming persistence.
*
* @param theSession the the session
*/
public ResumingPersistence(Session theSession) {
setSession(theSession);
}
/**
* Adds the task.
*
* @param task the task
*/
/* (non-Javadoc)
* @see it.slumdroid.tool.components.persistence.StepDiskPersistence#addTask(it.slumdroid.droidmodels.model.Task)
*/
public void addTask(Task task) {
task.setFailed(false);
getSession().addTask(task);
saveStep();
}
/**
* Backup.
*
* @param original the original
* @return the string
*/
private String backup(String original) {
return original + ".bak";
}
/**
* Backup file.
*
* @param fileName the file name
*/
private void backupFile(String fileName) {
copy(fileName, backup(fileName));
}
/**
* Can has resume.
*
* @return true, if successful
*/
public boolean canHasResume() {
if (!exists(GUI_TREE_FILE_NAME)) {
return false;
}
if (exists(backup(TASK_LIST_FILE_NAME))) {
restoreFile(TASK_LIST_FILE_NAME);
if (exists(backup(ACTIVITY_LIST_FILE_NAME))) {
restoreFile(ACTIVITY_LIST_FILE_NAME);
}
}
if (!exists(TASK_LIST_FILE_NAME)) {
return false;
}
return true;
}
/**
* Capture image.
*
* @return the bitmap
*/
private Bitmap captureImage() {
ArrayList<View> views = Automation.getRobotium().getViews();
Bitmap source = null;
Bitmap bitmap = null;
try{
if (views != null && views.size() > 0) {
final View view = views.get(0);
view.destroyDrawingCache();
view.buildDrawingCache(false);
source = view.getDrawingCache();
if (source == null) {
return null;
}
Bitmap.Config config = source.getConfig();
if (config == null){
config = Bitmap.Config.ARGB_8888;
}
bitmap = source.copy(config, false);
source.recycle();
view.destroyDrawingCache();
return bitmap;
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* Close file.
*/
private void closeFile() {
closeFile (this.fOut, this.osw);
}
/**
* Close file.
*
* @param theFile the the file
* @param theStream the the stream
*/
private void closeFile(FileOutputStream theFile, OutputStreamWriter theStream) {
try {
theStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
theStream.close();
theFile.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Close file.
*
* @param theFile the the file
* @param theStream the the stream
*/
private void closeFile(OutputStream theFile, OutputStream theStream) {
try {
theStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
theStream.close();
theFile.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Close state file.
*/
private void closeStateFile() {
closeFile(this.stateFile, this.stateStream);
}
/**
* Close task file.
*/
private void closeTaskFile() {
closeFile(this.taskFile, this.taskStream);
}
/**
* Copy.
*
* @param from the from
* @param to the to
*/
private void copy(String from, String to) {
FileInputStream in = null;
FileOutputStream out = null;
byte[] buffer = new byte[4096];
try {
in = this.wrapper.openFileInput(from);
out = this.wrapper.openFileOutput(to, ContextWrapper.MODE_PRIVATE);
int reader = 0;
while ((reader = in.read(buffer)) != -1) {
out.write(buffer, 0, reader);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Delete.
*
* @param fileName the file name
* @return true, if successful
*/
private boolean delete(String fileName) {
return this.wrapper.deleteFile(fileName);
}
/**
* Exists.
*
* @param filename the filename
* @return true, if successful
*/
/* (non-Javadoc)
* @see it.slumdroid.tool.model.Persistence#exists(java.lang.String)
*/
public boolean exists(String filename) {
return this.wrapper.getFileStreamPath(filename).exists();
}
/**
* Generate.
*
* @return the string
*/
private String generate() {
String graph = new String();
try {
if (this.theSession instanceof XmlGraph) {
graph = ((XmlGraph)this.theSession).toXml();
} else {
graph = this.theSession.toString();
}
} catch (Exception e) {
e.printStackTrace();
}
if (isFirst() && isLast()) {
return graph;
}
int bodyBegin = graph.indexOf(XML_BODY_BEGIN);
int bodyEnd = graph.lastIndexOf(XML_BODY_END) + XML_BODY_END.length();
if (isFirst()) {
this.footer = graph.substring(bodyEnd);
return graph.substring(0, bodyEnd);
}
if (isLast()) {
return (bodyBegin == -1)?(this.footer):graph.substring(bodyBegin);
}
if ((bodyBegin == -1)
|| (bodyEnd == -1)) { // Empty body
return new String();
}
return graph.substring(bodyBegin, bodyEnd) + System.getProperty("line.separator");
}
/* (non-Javadoc)
* @see it.slumdroid.tool.model.SaveStateListener#getListenerName()
*/
public String getListenerName() {
return ACTOR_NAME;
}
/**
* Gets the session.
*
* @return the session
*/
public Session getSession() {
return this.theSession;
}
/**
* Checks if is first.
*
* @return true, if is first
*/
private boolean isFirst() {
return this.first;
}
/**
* Checks if is last.
*
* @return true, if is last
*/
private boolean isLast() {
return this.last && noTasks();
}
/**
* Load parameters.
*/
@SuppressWarnings("unchecked")
public void loadParameters() {
FileInputStream theFile = null;
ObjectInputStream theStream = null;
try {
theFile = wrapper.openFileInput(PARAMETERS_FILE_NAME);
theStream = new ObjectInputStream(theFile);
this.parameters = (Map<String, SessionParams>) theStream.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
theFile.close();
theStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
for (Entry<String, SaveStateListener> listener: this.theListeners.entrySet()) {
listener.getValue().onLoadingState(this.parameters.get(listener.getKey()));
}
}
/**
* No tasks.
*
* @return true, if successful
*/
private boolean noTasks() {
return this.taskList.size() == 0;
}
/* (non-Javadoc)
* @see it.slumdroid.tool.model.SaveStateListener#onLoadingState(it.slumdroid.tool.utilities.SessionParams)
*/
public void onLoadingState(SessionParams sessionParams) {
this.footer = sessionParams.get(PARAM_NAME);
}
/* (non-Javadoc)
* @see it.slumdroid.tool.model.StateDiscoveryListener#onNewState(it.slumdroid.droidmodels.model.ActivityState)
*/
public void onNewState(ActivityState newState) {
if (exists(ACTIVITY_LIST_FILE_NAME)) {
backupFile(ACTIVITY_LIST_FILE_NAME);
}
try {
openStateFile(newState instanceof FinalActivity);
String xml = ((ElementWrapper)newState).toXml() + System.getProperty("line.separator");
writeOnStateFile(xml);
closeStateFile();
} catch (Exception e) {
e.printStackTrace();
}
}
/* (non-Javadoc)
* @see it.slumdroid.tool.model.SaveStateListener#onSavingState()
*/
@Override
public SessionParams onSavingState() {
return new SessionParams(PARAM_NAME, this.footer);
}
/* (non-Javadoc)
* @see it.slumdroid.tool.model.DispatchListener#onTaskDispatched(it.slumdroid.droidmodels.model.Task)
*/
public void onTaskDispatched(Task task) {
task.setFailed(true);
saveTaskList();
saveParameters();
}
/**
* Open file.
*
* @param fileName the file name
*/
private void openFile(String fileName) {
try{
this.fOut = this.wrapper.openFileOutput(fileName, this.mode);
this.osw = new OutputStreamWriter(this.fOut);
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* Open state file.
*
* @param append the append
*/
private void openStateFile(boolean append) {
try{
this.stateFile = wrapper.openFileOutput(ACTIVITY_LIST_FILE_NAME, (append)?ContextWrapper.MODE_APPEND:ContextWrapper.MODE_PRIVATE);
this.stateStream = new OutputStreamWriter(this.stateFile);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Open task file.
*/
private void openTaskFile() {
try{
this.taskFile = wrapper.openFileOutput(TASK_LIST_FILE_NAME, ContextWrapper.MODE_PRIVATE);
this.taskStream = new OutputStreamWriter(this.taskFile);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Read file.
*
* @param input the input
* @return the list
*/
private List<String> readFile(String input) {
FileInputStream theFile = null;
BufferedReader theStream = null;
String line = new String();
List<String> output = new ArrayList<String>();
try{
theFile = wrapper.openFileInput(input);
theStream = new BufferedReader(new FileReader(theFile.getFD()));
while ((line = theStream.readLine()) != null) {
output.add(line);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
theFile.close();
theStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return output;
}
/**
* Read state file.
*
* @return the list
*/
public List<String> readStateFile() {
return readFile(ACTIVITY_LIST_FILE_NAME);
}
/**
* Read task file.
*
* @return the list
*/
public List<String> readTaskFile() {
return readFile(TASK_LIST_FILE_NAME);
}
/**
* Register listener.
*
* @param listener the listener
*/
public void registerListener(SaveStateListener listener) {
theListeners.put(listener.getListenerName(), listener);
}
/**
* Restore file.
*
* @param fileName the file name
*/
private void restoreFile(String fileName) {
copy(backup(fileName), fileName);
}
/**
* Save.
*/
/* (non-Javadoc)
* @see it.slumdroid.tool.components.persistence.StepDiskPersistence#save()
*/
public void save() {
save(true);
saveParameters();
saveTaskList();
if (noTasks()) {
delete(backup(ACTIVITY_LIST_FILE_NAME));
delete(PARAMETERS_FILE_NAME);
delete(backup(PARAMETERS_FILE_NAME));
delete(TASK_LIST_FILE_NAME);
delete(backup(TASK_LIST_FILE_NAME));
}
}
/**
* Save.
*
* @param last the last
*/
private void save(boolean last) {
if (!isFirst()) {
this.mode = ContextWrapper.MODE_APPEND;
}
if (last) {
setLast();
}
save(GUI_TREE_FILE_NAME);
}
/**
* Save.
*
* @param fileName the file name
*/
protected void save(String fileName) {
String graph = generate();
openFile(fileName);
try {
writeOnFile(graph);
} catch (IOException e) {
e.printStackTrace();
} finally {
closeFile();
}
}
/**
* Save image.
*
* @param image the image
* @param name the name
* @throws IOException Signals that an I/O exception has occurred.
*/
public void saveImage(Bitmap image, String name) throws IOException {
FileOutputStream fileOutput = null;
OutputStreamWriter streamWriter = null;
try {
fileOutput = wrapper.openFileOutput(name, ContextWrapper.MODE_PRIVATE);
streamWriter = new OutputStreamWriter(fileOutput);
if (fileOutput != null) {
image.compress(Bitmap.CompressFormat.PNG, 50, fileOutput);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fileOutput != null) {
streamWriter.close();
fileOutput.close();
}
}
}
/**
* Save parameters.
*/
private void saveParameters() {
parameters.clear();
FileOutputStream theFile = null;
ObjectOutputStream theStream = null;
for (Entry<String, SaveStateListener> listener: this.theListeners.entrySet()) {
parameters.put(listener.getKey(), listener.getValue().onSavingState());
}
try {
theFile = wrapper.openFileOutput(PARAMETERS_FILE_NAME, ContextWrapper.MODE_PRIVATE);
theStream = new ObjectOutputStream(theFile);
theStream.writeObject(this.parameters);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (theFile != null && theStream != null) {
closeFile(theFile, theStream);
}
}
}
/**
* Save screenshot.
*
* @param fileName the file name
* @return true, if successful
*/
public boolean saveScreenshot(String fileName) {
Bitmap bitmap = captureImage();
if (bitmap == null) {
return false;
}
try {
saveImage(bitmap, fileName);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* Save step.
*/
public void saveStep() {
save(isLast());
for (Task task: getSession()) {
getSession().removeTask(task);
}
setNotFirst();
}
/**
* Save task list.
*/
private void saveTaskList() {
if (noTasks()) {
delete(TASK_LIST_FILE_NAME);
return;
}
if (exists(TASK_LIST_FILE_NAME)) {
backupFile(TASK_LIST_FILE_NAME);
}
try {
openTaskFile();
for (Task task: this.taskList) {
String xml = ((ElementWrapper)task).toXml() + System.getProperty("line.separator");
writeOnTaskFile(xml);
}
closeTaskFile();
} catch (Exception e) {
e.printStackTrace();
}
if (exists(backup(TASK_LIST_FILE_NAME))) {
delete(backup(TASK_LIST_FILE_NAME));
}
if (exists(backup(ACTIVITY_LIST_FILE_NAME))) {
delete(backup(ACTIVITY_LIST_FILE_NAME));
}
}
/**
* Sets the context.
*
* @param activity the new context
*/
/* (non-Javadoc)
* @see it.slumdroid.tool.model.Persistence#setContext(android.app.Activity)
*/
public void setContext(Activity activity) {
this.wrapper = new ContextWrapper(activity);
}
/**
* Sets the last.
*/
private void setLast() {
this.last = true;
}
/**
* Sets the not first.
*/
public void setNotFirst() {
this.first = false;
}
/**
* Sets the session.
*
* @param session the new session
*/
/* (non-Javadoc)
* @see it.slumdroid.tool.model.Persistence#setSession(it.slumdroid.droidmodels.model.Session)
*/
public void setSession(Session session) {
this.theSession = session;
}
/**
* Sets the task list.
*
* @param taskList the new task list
*/
public void setTaskList(List<Task> taskList) {
this.taskList = taskList;
}
/**
* Write on file.
*
* @param graph the graph
* @throws IOException Signals that an I/O exception has occurred.
*/
private void writeOnFile(String graph) throws IOException {
writeOnFile(this.osw, graph);
}
/**
* Write on file.
*
* @param output the output
* @param graph the graph
* @throws IOException Signals that an I/O exception has occurred.
*/
private void writeOnFile(OutputStreamWriter output, String graph) throws IOException {
output.write(graph);
}
/**
* Write on state file.
*
* @param graph the graph
* @throws IOException Signals that an I/O exception has occurred.
*/
private void writeOnStateFile(String graph) throws IOException {
writeOnFile(this.stateStream, graph);
}
/**
* Write on task file.
*
* @param graph the graph
* @throws IOException Signals that an I/O exception has occurred.
*/
private void writeOnTaskFile(String graph) throws IOException {
writeOnFile(this.taskStream, graph);
}
}