/*
* 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.android.tools.sdkcontroller.handlers;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import android.content.Context;
import android.os.Message;
import android.util.Log;
import com.android.tools.sdkcontroller.lib.EmulatorConnection;
import com.android.tools.sdkcontroller.lib.EmulatorListener;
import com.android.tools.sdkcontroller.service.ControllerService;
/**
* An abstract base class for all "action handlers".
*
* The {@link ControllerService} can deal with several handlers, each have a specific
* purpose as described by {@link HandlerType}.
*
* The {@link BaseHandler} class adds support for activities to connect by providing
* an {@link android.os.Handler} (which we'll call a "UI Handler" to differentiate it
* from our "Service Handler"). The service handler will provide events via this
* UI handler directly on the activity's UI thread.
*
* The {@link BaseHandler} keeps track of the current {@link EmulatorConnection} given
* via {@link #onStart(EmulatorConnection, Context)}.
*
* The {@link BaseHandler} provides a simple way for activities to send event messages
* back to the emulator by using {@link #sendEventToEmulator(String)}. This method
* is safe to call from any thread, even the UI thread.
*/
public abstract class BaseHandler {
protected static final boolean DEBUG = false;
protected static final String TAG = null;
private EmulatorConnection mConnection;
private final AtomicInteger mEventCount = new AtomicInteger(0);
private volatile boolean mRunEventQueue = true;
private final BlockingQueue mEventQueue = new LinkedBlockingQueue();
private static String EVENT_QUEUE_END = "@end@";
private final List mUiHandlers = new ArrayList();
private final HandlerType mHandlerType;
private final Thread mEventThread;
private int mPort;
/**
* The type of action that this handler manages.
*/
public enum HandlerType {
/** A handler to send multitouch events from the device to the emulator and display
* the emulator screen on the device. */
MultiTouch,
/** A handler to send sensor events from the device to the emulaotr. */
Sensor
}
/**
* Initializes a new base handler.
*
* @param type A non-null {@link HandlerType} value.
* @param port A non-null communication port number.
*/
protected BaseHandler(HandlerType type, int port) {
mHandlerType = type;
mPort = port;
final String name = type.toString();
mEventThread = new Thread(new Runnable() {
@Override
public void run() {
if (DEBUG) Log.d(TAG, "EventThread.started-" + name);
while(mRunEventQueue) {
try {
String msg = mEventQueue.take();
if (msg != null && mConnection != null && !msg.equals(EVENT_QUEUE_END)) {
mConnection.sendNotification(msg);
mEventCount.incrementAndGet();
}
} catch (InterruptedException e) {
Log.e(TAG, "EventThread-" + name, e);
}
}
if (DEBUG) Log.d(TAG, "EventThread.terminate-" + name);
}
}, "EventThread-" + name);
}
/**
* Returns the type of this handler, as given to the constructor.
*
* @return One of the {@link HandlerType} values.
*/
public HandlerType getType() {
return mHandlerType;
}
/**
* Returns he communication port used by this handler to communicate with the emulator,
* as given to the constructor.
*
* Note that right now we have 2 handlers that each use their own port. The goal is
* to move to a single-connection mechanism where all the handlers' data will be
* multiplexed on top of a single {@link EmulatorConnection}.
*
* @return A non-null port value.
*/
public int getPort() {
return mPort;
}
/**
* Returns the last {@link EmulatorConnection} passed to
* {@link #onStart(EmulatorConnection, Context)}.
* It becomes null when {@link #onStop()} is called.
*
* @return The current {@link EmulatorConnection}.
*/
public EmulatorConnection getConnection() {
return mConnection;
}
/**
* Called once the {@link EmulatorConnection} has been successfully initialized.
*
* Note that this will not be called if the {@link EmulatorConnection}
* fails to bind to the underlying socket.
*
* This base implementation keeps tracks of the connection.
*
* @param connection The connection that has just been created.
* A handler might want to use this to send data to the emulator via
* {@link EmulatorConnection#sendNotification(String)}. However handlers
* need to be particularly careful in not sending network data
* from the main UI thread.
* @param context The controller service' context.
* @see #getConnection()
*/
public void onStart(EmulatorConnection connection, Context context) {
assert connection != null;
mConnection = connection;
mRunEventQueue = true;
mEventThread.start();
}
/**
* Called once the {@link EmulatorConnection} is being disconnected.
* This nullifies the connection returned by {@link #getConnection()}.
*/
public void onStop() {
// Stop the message queue
mConnection = null;
if (mRunEventQueue) {
mRunEventQueue = false;
mEventQueue.offer(EVENT_QUEUE_END);
}
}
public int getEventSentCount() {
return mEventCount.get();
}
/**
* Utility for handlers or activities to sends a string event to the emulator.
* This method is safe for the activity to call from any thread, including the UI thread.
*
* @param msg Event message. Must not be null.
*/
public void sendEventToEmulator(String msg) {
try {
mEventQueue.put(msg);
} catch (InterruptedException e) {
Log.e(TAG, "EventQueue.put", e);
}
}
// ------------
// Interaction from the emulator connection towards the handler
/**
* Emulator query being forwarded to the handler.
*
* @see EmulatorListener#onEmulatorQuery(String, String)
*/
public abstract String onEmulatorQuery(String query, String param);
/**
* Emulator blob query being forwarded to the handler.
*
* @see EmulatorListener#onEmulatorBlobQuery(byte[])
*/
public abstract String onEmulatorBlobQuery(byte[] array);
// ------------
// Interaction from handler towards listening UI
/**
* Indicates whether the given UI handler is already registered with the handler.
*
* @param uiHandler The UI handler.
* @return True if the UI handler is not null and already registered.
*/
public boolean hasUiHandler(android.os.Handler uiHandler) {
return uiHandler != null && mUiHandlers.contains(uiHandler);
}
/**
* Registers a new UI handler.
*
* @param uiHandler A non-null UI handler to register.
* Ignored if the UI handler is null or already registered.
*/
public void addUiHandler(android.os.Handler uiHandler) {
assert uiHandler != null;
if (uiHandler != null) {
if (!mUiHandlers.contains(uiHandler)) {
mUiHandlers.add(uiHandler);
}
}
}
/**
* Unregisters an UI handler.
*
* @param uiHandler A non-null UI listener to unregister.
* Ignored if the listener is null or already registered.
*/
public void removeUiHandler(android.os.Handler uiHandler) {
assert uiHandler != null;
mUiHandlers.remove(uiHandler);
}
/**
* Protected method to be used by handlers to send an event to all UI handlers.
*
* @param event An integer event code with no specific parameters.
* To be defined by the handler itself.
*/
protected void notifyUiHandlers(int event) {
for (android.os.Handler uiHandler : mUiHandlers) {
uiHandler.sendEmptyMessage(event);
}
}
/**
* Protected method to be used by handlers to send an event to all UI handlers.
*
* @param msg An event with parameters. To be defined by the handler itself.
*/
protected void notifyUiHandlers(Message msg) {
for (android.os.Handler uiHandler : mUiHandlers) {
uiHandler.sendMessage(msg);
}
}
}