blob: 0bb31bd7f13147443c20bfdd8a8542c6c63bcd11 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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 com.motorola.studio.android.emulator.logic;
import static com.motorola.studio.android.common.log.StudioLogger.debug;
import static com.motorola.studio.android.common.log.StudioLogger.error;
import static com.motorola.studio.android.common.log.StudioLogger.info;
import static com.motorola.studio.android.common.log.StudioLogger.warn;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.IJobManager;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.sequoyah.vnc.protocol.PluginProtocolActionDelegate;
import org.eclipse.sequoyah.vnc.protocol.lib.IProtocolExceptionHandler;
import org.eclipse.sequoyah.vnc.protocol.lib.ProtocolHandle;
import org.eclipse.sequoyah.vnc.protocol.lib.exceptions.InvalidDefinitionException;
import org.eclipse.sequoyah.vnc.protocol.lib.exceptions.InvalidInputStreamDataException;
import org.eclipse.sequoyah.vnc.protocol.lib.exceptions.InvalidMessageException;
import org.eclipse.sequoyah.vnc.protocol.lib.exceptions.MessageHandleException;
import org.eclipse.sequoyah.vnc.protocol.lib.exceptions.ProtocolHandshakeException;
import org.eclipse.sequoyah.vnc.protocol.lib.exceptions.ProtocolRawHandlingException;
import com.motorola.studio.android.adt.ISerialNumbered;
import com.motorola.studio.android.common.utilities.EclipseUtils;
import com.motorola.studio.android.emulator.core.exception.InstanceNotFoundException;
import com.motorola.studio.android.emulator.core.exception.InstanceStopException;
import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance;
import com.motorola.studio.android.emulator.core.utils.EmulatorCoreUtils;
import com.motorola.studio.android.emulator.i18n.EmulatorNLS;
import com.motorola.studio.android.emulator.logic.AbstractStartAndroidEmulatorLogic.LogicMode;
/**
* DESCRIPTION:
* Class that defines how to handle internal protocol exceptions
*
* RESPONSABILITY:
* Handle internal protocol exceptions
*
* COLABORATORS:
* None.
*
* USAGE:
* This class shall be used by Eclipse only
*/
public class AndroidExceptionHandler implements IProtocolExceptionHandler
{
private int handlingLevel = 1;
private boolean checkThreadRunning = false;
private Lock lock = new ReentrantReadWriteLock().writeLock();
private static Collection<String> stoppedWithFailure = new HashSet<String>();
static
{
IJobManager manager = Job.getJobManager();
manager.addJobChangeListener(new JobChangeAdapter()
{
@Override
public void done(IJobChangeEvent event)
{
Job job = event.getJob();
if (job.belongsTo(StartVncServerLogic.VNC_SERVER_JOB_FAMILY))
{
IStatus result = event.getResult();
if (!result.isOK() && !(result.getSeverity() == IStatus.CANCEL))
{
stoppedWithFailure.add(job.getName());
}
}
}
@Override
public void scheduled(IJobChangeEvent event)
{
Job job = event.getJob();
if (job.belongsTo(StartVncServerLogic.VNC_SERVER_JOB_FAMILY))
{
stoppedWithFailure.remove(job.getName());
}
}
});
}
/**
* Handles internal IOExceptions caught by the protocol plugin during its execution.
*
* @see IProtocolExceptionHandler#handleIOException(ProtocolHandle, IOException)
*/
public void handleIOException(ProtocolHandle handle, IOException e)
{
error("A socket was broken while communicating to server. Cause: " + e.getMessage());
handleException(handle);
}
/**
* Handles exceptions thrown by the protocol plugin when it detects an invalid message
* definition provided by the plugin which is extending it (in this case, the core plugin).
*
* @see IProtocolExceptionHandler#handleInvalidDefinitionException(ProtocolHandle, InvalidDefinitionException)
*/
public void handleInvalidDefinitionException(ProtocolHandle handle, InvalidDefinitionException e)
{
// This exception should not happen, because the message definitions are provided
// by the development team.
warn("An invalid message definition was detected. Cause: " + e.getMessage());
handleException(handle);
}
/**
* Handles exceptions thrown by the protocol plugin when it detects that the data retrieved from
* the connection does not match the format defined in the message definition.
*
* @see IProtocolExceptionHandler#handleInvalidInputStreamDataException(ProtocolHandle, InvalidInputStreamDataException)
*/
public void handleInvalidInputStreamDataException(ProtocolHandle handle,
InvalidInputStreamDataException e)
{
// If the data retrieved from the connection is not as expected (considering
// the message definition provided), there is a high chance of errors to happen.
// It is likely that the data from stream is no longer synchronized, so the
// exception handling for this case is to restart connection.
error("Some received data is not compatible with the expected definition. Restarting the protocol for synchronization.");
handleException(handle);
}
/**
* Handles exceptions thrown by the protocol plugin when it detects that the message
* provided for sending does not have enough or valid information, given a corresponding
* message definition
*
* @see IProtocolExceptionHandler#handleInvalidMessageException(ProtocolHandle, InvalidMessageException)
*/
public void handleInvalidMessageException(ProtocolHandle handle, InvalidMessageException e)
{
// This exception should not happen, because the message object data is provided
// by the development team. Log only.
warn("A message was not constructed according to its definition. Cause: " + e.getMessage());
handleException(handle);
}
/**
* Handles exceptions thrown by any message handler when they discovers that it
* is not possible to handle the message and it is a fatal error for the protocol.
*
* @see IProtocolExceptionHandler#handleMessageHandleException(ProtocolHandle, MessageHandleException)
*/
public void handleMessageHandleException(ProtocolHandle handle, MessageHandleException e)
{
// If a message handler throws a MessageHandleException, that means that it cannot
// continue. Restart the protocol to guarantee the synchronization.
error("A message handler has ended in error and has thrown an exception meaning the protocol cannot continue.");
handleException(handle);
}
/**
* Handles exceptions thrown by the protocol plugin when it is not possible to
* init the protocol, for example because the handshaking procedure has failed.
*
* @see IProtocolExceptionHandler#handleProtocolHandshakeException(ProtocolHandle, ProtocolHandshakeException)
*/
public void handleProtocolHandshakeException(ProtocolHandle handle, ProtocolHandshakeException e)
{
error("Could not initialize the protocol.");
handleException(handle);
}
/**
* Handles exceptions thrown by any raw field handler when they discovers that it
* is not possible to handle the field and it is a fatal error for the protocol.
*
* @see IProtocolExceptionHandler#handleProtocolRawHandlingException(ProtocolHandle, ProtocolRawHandlingException)
*/
public void handleProtocolRawHandlingException(ProtocolHandle handle,
ProtocolRawHandlingException e)
{
// This message should be thrown by raw field handlers when they cannot handle the
// raw field and need to abort the protocol execution. Restart the protocol to
// guarantee the synchronization.
error("A raw field handler has ended in error and has thrown an exception meaning the protocol cannot continue.");
handleException(handle);
}
/**
* This method will be called whenever an exception happens. It is important to find
* out if the failure happened during an start or restart procedure, so that we can
* do appropriate handling to each situation.
*
* @param handle The object that identifies the protocol instance
*/
private void handleException(ProtocolHandle handle)
{
IAndroidEmulatorInstance instance = null;
if (lock.tryLock())
{
try
{
instance = EmulatorCoreUtils.getAndroidInstanceByHandle(handle);
try
{
debug("Check if device is online: " + instance);
if (instance instanceof ISerialNumbered)
{
String serialNumber = ((ISerialNumbered) instance).getSerialNumber();
AndroidLogicUtils.testDeviceStatus(serialNumber);
}
}
catch (Exception e)
{
error("Device is not online. Abort VNC session...");
abort(instance);
}
if ((handlingLevel == 3) && canRestartServer(instance))
{
handlingLevel--;
}
// Firstly, try to restart only the VNC client. If restarting the VNC client
// is not possible, try to restart the VNC server at the device and to connect
// to it again. If the start logic cannot be retrieved, delegate the decision
// to the user.
if (handlingLevel == 1)
{
restartClientOnly(handle);
handlingLevel++;
}
else if (handlingLevel == 2)
{
restartServerAndClient(instance, handle);
handlingLevel++;
}
else
{
if (delegateDecisionToUser(handle))
{
abort(instance);
}
}
}
catch (InstanceNotFoundException e)
{
// If the instance is not found, it means that the instance is stopped.
// In this case, a restart is not applicable.
}
finally
{
lock.unlock();
}
}
}
private boolean canRestartServer(IAndroidEmulatorInstance instance)
{
String name = StartVncServerLogic.VNC_SERVER_JOB_PREFIX + instance.getName();
return !stoppedWithFailure.contains(name);
}
private void restartClientOnly(final ProtocolHandle handle)
{
PluginProtocolActionDelegate.requestRestartProtocol(handle);
if (!checkThreadRunning)
{
Runnable r = new Runnable()
{
public void run()
{
checkThreadRunning = true;
while (checkThreadRunning)
{
if (PluginProtocolActionDelegate.isProtocolRunning(handle))
{
handlingLevel = 1;
checkThreadRunning = false;
}
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
// Do nothing.
}
}
}
};
(new Thread(r)).start();
}
}
private void restartServerAndClient(IAndroidEmulatorInstance instance, ProtocolHandle handle)
{
try
{
if (instance instanceof IAndroidLogicInstance)
{
IAndroidLogicInstance logicInstance = (IAndroidLogicInstance) instance;
AbstractStartAndroidEmulatorLogic logic = logicInstance.getStartLogic();
logic.execute(logicInstance, LogicMode.TRANSFER_AND_CONNECT_VNC, logicInstance
.getTimeout(), new NullProgressMonitor());
try
{
Thread.sleep(1500);
}
catch (InterruptedException e)
{
// Do nothing.
}
PluginProtocolActionDelegate.requestRestartProtocol(handle);
}
else
{
handlingLevel = 3;
}
}
catch (Exception e1)
{
handlingLevel = 3;
}
}
/**
* In this method, the user is asked whether to retry or not. While the user does not
* give up, the instance retries to connect to the emulator. When the user gives up,
* the instance is stopped.
*
* @param handle The object that identifies the protocol instance
*/
private boolean delegateDecisionToUser(ProtocolHandle handle)
{
boolean abort = true;
error("Cannot reconnect to VM. Asking to the user if he/she wants to retry.");
if (EclipseUtils.showQuestionDialog(EmulatorNLS.GEN_Question,
EmulatorNLS.QUESTION_AndroidExceptionHandler_ImpossibleToReconnect))
{
info("User chose to retry to reconnect to emulator VNC server.");
PluginProtocolActionDelegate.requestRestartProtocol(handle);
handlingLevel = 2;
abort = false;
}
return abort;
}
/**
*
*/
private void abort(IAndroidEmulatorInstance instance)
{
info("User chose to stop the instance.");
try
{
checkThreadRunning = false;
instance.stop(true);
}
catch (InstanceStopException e1)
{
error("Error while running service for stopping virtual machine");
EclipseUtils.showErrorDialog(EmulatorNLS.GEN_Error,
EmulatorNLS.EXC_AndroidExceptionHandler_CannotRunStopService);
}
}
}