/* * Copyright 2011 Ytai Ben-Tsvi. All rights reserved. * * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARSHAN POURSOHI OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied. */ package ioio.lib.util; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import java.util.Collection; import java.util.LinkedList; import ioio.lib.api.IOIO; import ioio.lib.api.IOIOFactory; import ioio.lib.api.exception.ConnectionLostException; import ioio.lib.api.exception.IncompatibilityException; import ioio.lib.spi.IOIOConnectionBootstrap; import ioio.lib.spi.IOIOConnectionFactory; import ioio.lib.spi.Log; import ioio.lib.util.android.ContextWrapperDependent; /** * A convenience class for easy creation of IOIO-based applications. * <p/> * It is used by creating a concrete Activity in your application, which extends * this class. This class then takes care of proper creation and abortion of the * IOIO connection and of a dedicated thread for IOIO communication. * <p/> * In the basic usage the client should extend this class and implement * {@link #createIOIOThread()}, which should return an implementation of the * {@link AbstractIOIOActivity.IOIOThread} abstract class. In this * implementation, the client implements the * {@link AbstractIOIOActivity.IOIOThread#setup()} method, which gets called as * soon as communication with the IOIO is established, and the * {@link AbstractIOIOActivity.IOIOThread#loop()} method, which gets called * repetitively as long as the IOIO is connected. Both methods should access the * {@link AbstractIOIOActivity.IOIOThread#ioio_} field for controlling the IOIO. * <p/> * In addition, the {@link AbstractIOIOActivity.IOIOThread#disconnected()} * method may be overridden in order to execute logic as soon as a disconnection * occurs for whichever reason. The * {@link AbstractIOIOActivity.IOIOThread#incompatible()} method may be * overridden in order to take action in case where a IOIO whose firmware is * incompatible with the IOIOLib version that application is built with. * <p/> * In a more advanced use case, more than one IOIO is available. In this case, a * thread will be created for each IOIO, whose semantics are as defined above. * If the client needs to be able to distinguish between them, it is possible to * override {@link #createIOIOThread(String, Object)} instead of * {@link #createIOIOThread()}. The first argument provided will contain the * connection class name, such as ioio.lib.impl.SocketIOIOConnection for a * connection established over a TCP socket (which is used over ADB). The second * argument will contain information specific to the connection type. For * example, in the case of SocketIOIOConnection, the second argument will * contain an {@link Integer} representing the local port number. * * @deprecated Please use {@link ioio.lib.util.android.IOIOActivity} instead. */ public abstract class AbstractIOIOActivity extends Activity { private static final String TAG = "AbstractIOIOActivity"; static { IOIOConnectionRegistry .addBootstraps(new String[]{ "ioio.lib.android.accessory.AccessoryConnectionBootstrap", "ioio.lib.android.bluetooth.BluetoothIOIOConnectionBootstrap"}); } private Collection<IOIOThread> threads_ = new LinkedList<IOIOThread>(); private Collection<IOIOConnectionBootstrap> bootstraps_ = IOIOConnectionRegistry .getBootstraps(); private IOIOConnectionFactory currentConnectionFactory_; /** * Subclasses should call this method from their own onCreate() if * overloaded. It takes care of connecting with the IOIO. */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); for (IOIOConnectionBootstrap bootstrap : bootstraps_) { if (bootstrap instanceof ContextWrapperDependent) { ((ContextWrapperDependent) bootstrap).onCreate(this); } } } /** * Subclasses should call this method from their own onDestroy() if * overloaded. It takes care of connecting with the IOIO. */ @Override protected void onDestroy() { for (IOIOConnectionBootstrap bootstrap : bootstraps_) { if (bootstrap instanceof ContextWrapperDependent) { ((ContextWrapperDependent) bootstrap).onDestroy(); } } super.onDestroy(); } /** * Subclasses should call this method from their own onStart() if * overloaded. It takes care of connecting with the IOIO. */ @Override protected void onStart() { super.onStart(); for (IOIOConnectionBootstrap bootstrap : bootstraps_) { if (bootstrap instanceof ContextWrapperDependent) { ((ContextWrapperDependent) bootstrap).open(); } } createAllThreads(); startAllThreads(); } /** * Subclasses should call this method from their own onStop() if overloaded. * It takes care of disconnecting from the IOIO. */ @Override protected void onStop() { abortAllThreads(); try { joinAllThreads(); } catch (InterruptedException e) { } for (IOIOConnectionBootstrap bootstrap : bootstraps_) { if (bootstrap instanceof ContextWrapperDependent) { ((ContextWrapperDependent) bootstrap).close(); } } super.onStop(); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { for (IOIOConnectionBootstrap bootstrap : bootstraps_) { if (bootstrap instanceof ContextWrapperDependent) { ((ContextWrapperDependent) bootstrap).open(); } } } } /** * Subclasses must either implement this method or its other overload by * returning a concrete subclass of {@link IOIOThread}. <code>null</code> * may be returned if the client is not interested to create a thread for * this IOIO. In multi-IOIO scenarios, where you want to identify which IOIO * the thread is for, consider using {@link #createIOIOThread()} instead. * * @return An implementation of {@link IOIOThread}, or <code>null</code> to * skip. */ protected IOIOThread createIOIOThread() { throw new RuntimeException( "Client must override on of the createIOIOThread overloads!"); } /** * Subclasses should implement this method by returning a concrete subclass * of {@link IOIOThread}. This overload is useful in multi-IOIO scenarios, * where you want to identify which IOIO the thread is for. The provided * arguments should provide enough information to be unique per connection. * <code>null</code> may be returned if the client is not interested to * connect a thread for this IOIO. This can be used in order to filter out * unwanted connections, for example if the application is only intended for * wireless connection, any wired connection attempts may be rejected, thus * saving resources used for listening for incoming wired connections. * * @param connectionType A unique name of the connection type. Typically, the * fully-qualified name of the connection class used to connect * to the IOIO. * @param extra A connection-type-specific object with extra information on * the specific connection. Should provide information that * enables distinguishing between different IOIO instances using * the same connection class. For example, a Bluetooth connection * type, might have the remote IOIO's Bluetooth name as extra. * @return An implementation of {@link IOIOThread}, or <code>null</code> to * skip. */ protected IOIOThread createIOIOThread(String connectionType, Object extra) { return createIOIOThread(); } /** * An abstract class, which facilitates a thread dedicated for communication * with a single physical IOIO device. */ protected abstract class IOIOThread extends Thread { /** * Subclasses should use this field for controlling the IOIO. */ protected IOIO ioio_; private boolean abort_ = false; private boolean connected_ = true; private final IOIOConnectionFactory connectionFactory_ = currentConnectionFactory_; /** * Subclasses should override this method for performing operations to * be done once as soon as IOIO communication is established. Typically, * this will include opening pins and modules using the openXXX() * methods of the {@link #ioio_} field. */ protected void setup() throws ConnectionLostException, InterruptedException { } /** * Subclasses should override this method for performing operations to * be done repetitively as long as IOIO communication persists. * Typically, this will be the main logic of the application, processing * inputs and producing outputs. */ protected void loop() throws ConnectionLostException, InterruptedException { sleep(100000); } /** * Subclasses should override this method for performing operations to * be done once as soon as IOIO communication is lost or closed. * Typically, this will include GUI changes corresponding to the change. * This method will only be called if setup() has been called. The * {@link #ioio_} member must not be used from within this method. This * method should not block for long, since it may cause an ANR. */ protected void disconnected() { } /** * Subclasses should override this method for performing operations to * be done if an incompatible IOIO firmware is detected. The * {@link #ioio_} member must not be used from within this method. This * method will only be called once, until a compatible IOIO is connected * (i.e. {@link #setup()} gets called). */ protected void incompatible() { } /** * Not relevant to subclasses. */ @Override public final void run() { super.run(); while (!abort_) { try { synchronized (this) { if (abort_) { break; } ioio_ = IOIOFactory.create(connectionFactory_ .createConnection()); } } catch (Exception e) { Log.e(TAG, "Failed to create IOIO, aborting IOIOThread!"); return; } // if we got here, we have a ioio_! try { ioio_.waitForConnect(); connected_ = true; setup(); while (!abort_) { loop(); } } catch (ConnectionLostException e) { } catch (InterruptedException e) { ioio_.disconnect(); } catch (IncompatibilityException e) { Log.e(TAG, "Incompatible IOIO firmware", e); incompatible(); // nothing to do - just wait until physical disconnection } catch (Exception e) { Log.e(TAG, "Unexpected exception caught", e); ioio_.disconnect(); break; } finally { try { ioio_.waitForDisconnect(); } catch (InterruptedException e1) { } synchronized (this) { ioio_ = null; } if (connected_) { disconnected(); connected_ = false; } } } Log.d(TAG, "IOIOThread is exiting"); } /** * Not relevant to subclasses. */ public synchronized final void abort() { abort_ = true; if (ioio_ != null) { ioio_.disconnect(); } if (connected_) { interrupt(); } } } private void abortAllThreads() { for (IOIOThread thread : threads_) { thread.abort(); } } private void joinAllThreads() throws InterruptedException { for (IOIOThread thread : threads_) { thread.join(); } } private void createAllThreads() { threads_.clear(); Collection<IOIOConnectionFactory> factories = IOIOConnectionRegistry .getConnectionFactories(); for (IOIOConnectionFactory factory : factories) { currentConnectionFactory_ = factory; IOIOThread thread = createIOIOThread(factory.getType(), factory.getExtra()); if (thread != null) { threads_.add(thread); } } } private void startAllThreads() { for (IOIOThread thread : threads_) { thread.start(); } } }