/* * 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); } } }