aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorRaphael <raphael@google.com>2012-03-20 16:38:46 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2012-03-20 16:38:46 -0700
commit90d46ad18662d418d0a9cd85afe854e73d884c9c (patch)
treeb3088e29e434c3855d1cc0928aacf5f39aa66918 /apps
parent50716ba47b8eb7d1b5c461a452b4ddf876d3ba64 (diff)
parentf7900cb4967f007c9d2034d442dbc63445e9200a (diff)
downloadsdk-90d46ad18662d418d0a9cd85afe854e73d884c9c.zip
sdk-90d46ad18662d418d0a9cd85afe854e73d884c9c.tar.gz
sdk-90d46ad18662d418d0a9cd85afe854e73d884c9c.tar.bz2
Merge "SdkController: MultiTouch handler+activity."
Diffstat (limited to 'apps')
-rwxr-xr-xapps/SdkController/SdkControllerApp/AndroidManifest.xml5
-rwxr-xr-xapps/SdkController/SdkControllerApp/res/layout/multitouch.xml30
-rwxr-xr-xapps/SdkController/SdkControllerApp/res/values-v11/styles_v11.xml26
-rwxr-xr-xapps/SdkController/SdkControllerApp/res/values/styles.xml26
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/BaseBindingActivity.java26
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MainActivity.java4
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java362
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MultitouchActivity.java39
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/SensorActivity.java73
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java187
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/MultiTouchHandler.java121
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/MultitouchHandler.java60
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/SensorsHandler.java104
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/service/ControllerService.java47
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/views/MultiTouchView.java231
15 files changed, 1077 insertions, 264 deletions
diff --git a/apps/SdkController/SdkControllerApp/AndroidManifest.xml b/apps/SdkController/SdkControllerApp/AndroidManifest.xml
index 72725dc..6d690cd 100755
--- a/apps/SdkController/SdkControllerApp/AndroidManifest.xml
+++ b/apps/SdkController/SdkControllerApp/AndroidManifest.xml
@@ -29,10 +29,9 @@
android:launchMode="singleInstance" />
<activity
- android:name=".activities.MultitouchActivity"
- android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+ android:name=".activities.MultiTouchActivity"
android:launchMode="singleInstance"
- android:screenOrientation="portrait" />
+ android:screenOrientation="portrait" android:theme="@style/Theme.MultiTouch"/>
<service
android:name=".service.ControllerService"
diff --git a/apps/SdkController/SdkControllerApp/res/layout/multitouch.xml b/apps/SdkController/SdkControllerApp/res/layout/multitouch.xml
new file mode 100755
index 0000000..90018c6
--- /dev/null
+++ b/apps/SdkController/SdkControllerApp/res/layout/multitouch.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" >
+
+ <com.android.tools.sdkcontroller.views.MultiTouchView
+ android:id="@+id/imageView"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" />
+
+ <TextView
+ android:id="@+id/textStatus"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <TextView
+ android:id="@+id/textError"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/textStatus"
+ android:gravity="center_horizontal"
+ android:background="#F00F"
+ android:padding="8dp"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="#FFF0" />
+
+</RelativeLayout>
diff --git a/apps/SdkController/SdkControllerApp/res/values-v11/styles_v11.xml b/apps/SdkController/SdkControllerApp/res/values-v11/styles_v11.xml
new file mode 100755
index 0000000..29c35ca
--- /dev/null
+++ b/apps/SdkController/SdkControllerApp/res/values-v11/styles_v11.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+
+<resources>
+
+ <style name="Theme.MultiTouch" parent="android:Theme.Holo.NoActionBar">
+ <item name="android:windowBackground">@android:color/transparent</item>
+ </style>
+
+</resources>
diff --git a/apps/SdkController/SdkControllerApp/res/values/styles.xml b/apps/SdkController/SdkControllerApp/res/values/styles.xml
new file mode 100755
index 0000000..da51b55
--- /dev/null
+++ b/apps/SdkController/SdkControllerApp/res/values/styles.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+
+<resources>
+
+ <style name="Theme.MultiTouch" parent="android:Theme.NoTitleBar">
+ <item name="android:windowBackground">@android:color/transparent</item>
+ </style>
+
+</resources>
diff --git a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/BaseBindingActivity.java b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/BaseBindingActivity.java
index 53e9158..ab5306d 100755
--- a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/BaseBindingActivity.java
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/BaseBindingActivity.java
@@ -52,10 +52,17 @@ public abstract class BaseBindingActivity extends Activity {
/**
* Called when the activity resumes.
* This automatically binds to the service, starting it as needed.
+ * <p/>
+ * Since on resume we automatically bind to the service, the {@link ServiceConnection}
+ * will is restored and {@link #onServiceConnected()} is called as necessary.
+ * Derived classes that need to initialize anything that is related to the service
+ * (e.g. getting their handler) should thus do so in {@link #onServiceConnected()} and
+ * <em>not</em> in {@link #onResume()} -- since binding to the service is asynchronous
+ * there is <em>no</em> guarantee that {@link #getServiceBinder()} returns non-null
+ * when this call finishes.
*/
@Override
protected void onResume() {
- if (DEBUG) Log.d(TAG, "onResume");
super.onResume();
bindToService();
}
@@ -66,7 +73,6 @@ public abstract class BaseBindingActivity extends Activity {
*/
@Override
protected void onPause() {
- if (DEBUG) Log.d(TAG, "onPause");
super.onPause();
unbindFromService();
}
@@ -81,13 +87,16 @@ public abstract class BaseBindingActivity extends Activity {
/**
* Called by the service once the activity is connected (bound) to it.
+ * <p/>
* When this is called, {@link #getServiceBinder()} returns a non-null binder that
* can be used by the activity to control the service.
*/
protected abstract void onServiceConnected();
/**
- * Called by the service when it is disconnected (unbound).
+ * Called by the service when it is forcibly disconnected OR when we know
+ * we're unbinding the service.
+ * <p/>
* When this is called, {@link #getServiceBinder()} returns a null binder and
* the activity should stop using that binder and remove any reference to it.
*/
@@ -101,14 +110,22 @@ public abstract class BaseBindingActivity extends Activity {
final ControllerListener listener = createControllerListener();
mServiceConnection = new ServiceConnection() {
+ /**
+ * Called when the service is connected.
+ * Allows us to retrieve the binder to talk to the service.
+ */
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG) Log.d(TAG, "Activity connected to service");
mServiceBinder = (ControllerBinder) service;
- mServiceBinder.addListener(listener);
+ mServiceBinder.addControllerListener(listener);
BaseBindingActivity.this.onServiceConnected();
}
+ /**
+ * Called when the service got disconnected, e.g. because it crashed.
+ * This is <em>not</em> called when we unbind from the service.
+ */
@Override
public void onServiceDisconnected(ComponentName name) {
if (DEBUG) Log.d(TAG, "Activity disconnected from service");
@@ -134,6 +151,7 @@ public abstract class BaseBindingActivity extends Activity {
protected void unbindFromService() {
if (mServiceConnection != null) {
if (DEBUG) Log.d(TAG, "unbind service");
+ mServiceConnection.onServiceDisconnected(null /*name*/);
unbindService(mServiceConnection);
mServiceConnection = null;
}
diff --git a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MainActivity.java b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MainActivity.java
index ad9632f..4769245 100755
--- a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MainActivity.java
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MainActivity.java
@@ -113,7 +113,7 @@ public class MainActivity extends BaseBindingActivity {
@Override
public void onClick(View v) {
// Open the multi-touch activity.
- Intent i = new Intent(MainActivity.this, MultitouchActivity.class);
+ Intent i = new Intent(MainActivity.this, MultiTouchActivity.class);
startActivity(i);
}
});
@@ -188,7 +188,7 @@ public class MainActivity extends BaseBindingActivity {
private void updateError() {
ControllerBinder binder = getServiceBinder();
- String error = binder == null ? "" : binder.getSensorErrors();
+ String error = binder == null ? "" : binder.getServiceError();
if (error == null) {
error = "";
}
diff --git a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java
new file mode 100755
index 0000000..be11151
--- /dev/null
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java
@@ -0,0 +1,362 @@
+/*
+ * 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.activities;
+
+import java.io.ByteArrayInputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Message;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.widget.TextView;
+
+import com.android.tools.sdkcontroller.R;
+import com.android.tools.sdkcontroller.handlers.BaseHandler.HandlerType;
+import com.android.tools.sdkcontroller.handlers.MultiTouchHandler;
+import com.android.tools.sdkcontroller.service.ControllerService.ControllerBinder;
+import com.android.tools.sdkcontroller.service.ControllerService.ControllerListener;
+import com.android.tools.sdkcontroller.views.MultiTouchView;
+
+/**
+ * Activity that controls and displays the {@link MultiTouchHandler}.
+ */
+public class MultiTouchActivity extends BaseBindingActivity
+ implements android.os.Handler.Callback {
+
+ @SuppressWarnings("hiding")
+ private static String TAG = MultiTouchActivity.class.getSimpleName();
+ private static boolean DEBUG = true;
+
+ /** Received frame is JPEG image. */
+ private static final int FRAME_JPEG = 1;
+ /** Received frame is RGB565 bitmap. */
+ private static final int FRAME_RGB565 = 2;
+ /** Received frame is RGB888 bitmap. */
+ private static final int FRAME_RGB888 = 3;
+
+ private volatile MultiTouchHandler mHandler;
+
+ private TextView mTextError;
+ private TextView mTextStatus;
+ private MultiTouchView mImageView;
+ /** Width of the emulator's display. */
+ private int mEmulatorWidth = 0;
+ /** Height of the emulator's display. */
+ private int mEmulatorHeight = 0;
+ /** Bitmap storage. */
+ private int[] mColors;
+
+ private final TouchListener mTouchListener = new TouchListener();
+ private final android.os.Handler mUiHandler = new android.os.Handler(this);
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.multitouch);
+ mImageView = (MultiTouchView) findViewById(R.id.imageView);
+ mTextError = (TextView) findViewById(R.id.textError);
+ mTextStatus = (TextView) findViewById(R.id.textStatus);
+ updateStatus("Waiting for connection");
+ }
+
+ @Override
+ protected void onResume() {
+ if (DEBUG) Log.d(TAG, "onResume");
+ // BaseBindingActivity.onResume will bind to the service.
+ // Note: any initialization related to the service or the handler should
+ // go in onServiceConnected() since in this call the service may not be
+ // bound yet.
+ super.onResume();
+ updateError();
+ }
+
+ @Override
+ protected void onPause() {
+ if (DEBUG) Log.d(TAG, "onPause");
+ // BaseBindingActivity.onResume will unbind from (but not stop) the service.
+ super.onPause();
+ mImageView.setEnabled(false);
+ updateStatus("Paused");
+ }
+
+ // ----------
+
+ @Override
+ protected void onServiceConnected() {
+ if (DEBUG) Log.d(TAG, "onServiceConnected");
+ mHandler = (MultiTouchHandler) getServiceBinder().getHandler(HandlerType.MultiTouch);
+ if (mHandler != null) {
+ mHandler.addUiHandler(mUiHandler);
+ }
+ }
+
+ @Override
+ protected void onServiceDisconnected() {
+ if (DEBUG) Log.d(TAG, "onServiceDisconnected");
+ if (mHandler != null) {
+ mHandler.removeUiHandler(mUiHandler);
+ mHandler = null;
+ }
+ }
+
+ @Override
+ protected ControllerListener createControllerListener() {
+ return new MultiTouchControllerListener();
+ }
+
+ // ----------
+
+ private class MultiTouchControllerListener implements ControllerListener {
+ @Override
+ public void onErrorChanged() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ updateError();
+ }
+ });
+ }
+
+ @Override
+ public void onStatusChanged() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ControllerBinder binder = getServiceBinder();
+ boolean connected = binder.isEmuConnected();
+ mImageView.setEnabled(connected);
+ updateStatus(connected ? "Emulated connected" : "Emulator disconnected");
+ }
+ });
+ }
+ }
+
+ // ----------
+
+ /**
+ * Implements OnTouchListener interface that receives touch screen events,
+ * and reports them to the emulator application.
+ */
+ class TouchListener implements OnTouchListener {
+ /**
+ * Touch screen event handler.
+ */
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ StringBuilder sb = new StringBuilder();
+ final int action = event.getAction();
+ final int action_code = action & MotionEvent.ACTION_MASK;
+ final int action_pid_index = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;
+
+ // Build message for the emulator.
+ switch (action_code) {
+ case MotionEvent.ACTION_MOVE:
+ sb.append("action=move");
+ for (int n = 0; n < event.getPointerCount(); n++) {
+ mImageView.constructEventMessage(sb, event, n);
+ }
+ break;
+ case MotionEvent.ACTION_DOWN:
+ sb.append("action=down");
+ mImageView.constructEventMessage(sb, event, action_pid_index);
+ break;
+ case MotionEvent.ACTION_UP:
+ sb.append("action=up pid=").append(event.getPointerId(action_pid_index));
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ sb.append("action=pdown");
+ mImageView.constructEventMessage(sb, event, action_pid_index);
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ sb.append("action=pup pid=").append(event.getPointerId(action_pid_index));
+ break;
+ default:
+ Log.w(TAG, "Unknown action type: " + action_code);
+ return true;
+ }
+
+ if (DEBUG) Log.d(TAG, sb.toString());
+
+ MultiTouchHandler h = mHandler;
+ if (h != null) {
+ h.sendEventToEmulator(sb.toString() + '\0');
+ }
+ return true;
+ }
+ } // TouchListener
+
+ /** Implementation of Handler.Callback */
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MultiTouchHandler.EVENT_MT_START:
+ MultiTouchHandler h = mHandler;
+ if (h != null) {
+ mHandler.setViewSize(mImageView.getWidth(), mImageView.getHeight());
+ mImageView.setOnTouchListener(mTouchListener);
+ }
+ break;
+ case MultiTouchHandler.EVENT_MT_STOP:
+ mImageView.setOnTouchListener(null);
+ break;
+ case MultiTouchHandler.EVENT_FRAME_BUFFER:
+ onFrameBuffer((byte[]) msg.obj);
+ break;
+ }
+ return true; // we consumed this message
+ }
+
+ /**
+ * Called when a BLOB query is received from the emulator.
+ * <p/>
+ * This query is used to deliver framebuffer updates in the emulator. The
+ * blob contains an update header, followed by the bitmap containing updated
+ * rectangle. The header is defined as MTFrameHeader structure in
+ * external/qemu/android/multitouch-port.h
+ * <p/>
+ * NOTE: This method is called from the I/O loop, so all communication with
+ * the emulator will be "on hold" until this method returns.
+ *
+ * TODO ===> CHECK that we can consume that array from a different thread than the producer's.
+ * E.g. does the produce reuse the same array or does it generate a new one each time?
+ *
+ * @param array contains BLOB data for the query.
+ */
+ private void onFrameBuffer(byte[] array) {
+ final ByteBuffer bb = ByteBuffer.wrap(array);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+
+ // Read frame header.
+ final int header_size = bb.getInt();
+ final int disp_width = bb.getInt();
+ final int disp_height = bb.getInt();
+ final int x = bb.getInt();
+ final int y = bb.getInt();
+ final int w = bb.getInt();
+ final int h = bb.getInt();
+ final int bpl = bb.getInt();
+ final int bpp = bb.getInt();
+ final int format = bb.getInt();
+
+ // Update application display.
+ updateDisplay(disp_width, disp_height);
+
+ if (format == FRAME_JPEG) {
+ /*
+ * Framebuffer is in JPEG format.
+ */
+
+ final ByteArrayInputStream jpg = new ByteArrayInputStream(bb.array());
+ // Advance input stream to JPEG image.
+ jpg.skip(header_size);
+ // Draw the image.
+ mImageView.drawJpeg(x, y, w, h, jpg);
+ } else {
+ /*
+ * Framebuffer is in a raw RGB format.
+ */
+
+ final int pixel_num = h * w;
+ // Advance stream to the beginning of framebuffer data.
+ bb.position(header_size);
+
+ // Make sure that mColors is large enough to contain the
+ // update bitmap.
+ if (mColors == null || mColors.length < pixel_num) {
+ mColors = new int[pixel_num];
+ }
+
+ // Convert the blob bitmap into bitmap that we will display.
+ if (format == FRAME_RGB565) {
+ for (int n = 0; n < pixel_num; n++) {
+ // Blob bitmap is in RGB565 format.
+ final int color = bb.getShort();
+ final int r = ((color & 0xf800) >> 8) | ((color & 0xf800) >> 14);
+ final int g = ((color & 0x7e0) >> 3) | ((color & 0x7e0) >> 9);
+ final int b = ((color & 0x1f) << 3) | ((color & 0x1f) >> 2);
+ mColors[n] = Color.rgb(r, g, b);
+ }
+ } else if (format == FRAME_RGB888) {
+ for (int n = 0; n < pixel_num; n++) {
+ // Blob bitmap is in RGB565 format.
+ final int r = bb.getChar();
+ final int g = bb.getChar();
+ final int b = bb.getChar();
+ mColors[n] = Color.rgb(r, g, b);
+ }
+ } else {
+ Log.w(TAG, "Invalid framebuffer format: " + format);
+ return;
+ }
+ mImageView.drawBitmap(x, y, w, h, mColors);
+ }
+ }
+
+ /**
+ * Updates application's screen accordingly to the emulator screen.
+ *
+ * @param e_width Width of the emulator screen.
+ * @param e_height Height of the emulator screen.
+ */
+ private void updateDisplay(int e_width, int e_height) {
+ if (e_width != mEmulatorWidth || e_height != mEmulatorHeight) {
+ mEmulatorWidth = e_width;
+ mEmulatorHeight = e_height;
+
+ boolean rotateDisplay = false;
+ int w = mImageView.getWidth();
+ int h = mImageView.getHeight();
+ if (w > h != e_width > e_height) {
+ rotateDisplay = true;
+ int tmp = w;
+ w = h;
+ h = tmp;
+ }
+
+ float dx = (float) w / (float) e_width;
+ float dy = (float) h / (float) e_height;
+ mImageView.setDxDy(dx, dy, rotateDisplay);
+ if (DEBUG) Log.d(TAG, "Dispay updated: " + e_width + " x " + e_height +
+ " -> " + w + " x " + h + " ratio: " +
+ dx + " x " + dy);
+ }
+ }
+
+ // ----------
+
+ private void updateStatus(String status) {
+ mTextStatus.setVisibility(status == null ? View.GONE : View.VISIBLE);
+ if (status != null) mTextStatus.setText(status);
+ }
+
+ private void updateError() {
+ ControllerBinder binder = getServiceBinder();
+ String error = binder == null ? "" : binder.getServiceError();
+ if (error == null) {
+ error = "";
+ }
+
+ mTextError.setVisibility(error.length() == 0 ? View.GONE : View.VISIBLE);
+ mTextError.setText(error);
+ }
+}
diff --git a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MultitouchActivity.java b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MultitouchActivity.java
deleted file mode 100755
index 62f5d68..0000000
--- a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MultitouchActivity.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.activities;
-
-import com.android.tools.sdkcontroller.handlers.MultitouchHandler;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-/**
- * Activity that controls and displays the {@link MultitouchHandler}.
- */
-public class MultitouchActivity extends Activity {
-
- public static String TAG = MultitouchActivity.class.getSimpleName();
- @SuppressWarnings("unused")
- private static boolean DEBUG = true;
-
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- //TODO setContentView(R.layout.multitouch);
- }
-}
diff --git a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/SensorActivity.java b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
index 7305e96..bd924dd 100755
--- a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
@@ -21,6 +21,8 @@ import java.util.List;
import java.util.Map;
import android.os.Bundle;
+import android.os.Message;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
@@ -31,7 +33,6 @@ import android.widget.TextView;
import com.android.tools.sdkcontroller.R;
import com.android.tools.sdkcontroller.handlers.BaseHandler.HandlerType;
-import com.android.tools.sdkcontroller.handlers.BaseHandler.UiListener;
import com.android.tools.sdkcontroller.handlers.SensorsHandler;
import com.android.tools.sdkcontroller.handlers.SensorsHandler.MonitoredSensor;
import com.android.tools.sdkcontroller.service.ControllerService.ControllerBinder;
@@ -43,20 +44,21 @@ import com.android.tools.sdkcontroller.service.ControllerService.ControllerListe
* by the emulator. The user can select whether the sensor is active. It also displays
* data from the sensor when available.
*/
-public class SensorActivity extends BaseBindingActivity {
+public class SensorActivity extends BaseBindingActivity
+ implements android.os.Handler.Callback {
@SuppressWarnings("hiding")
public static String TAG = SensorActivity.class.getSimpleName();
- @SuppressWarnings("unused")
private static boolean DEBUG = true;
private TableLayout mTableLayout;
private TextView mTextError;
private TextView mTextStatus;
private SensorsHandler mSensorHandler;
- private final OurUiListener mUiListener = new OurUiListener();
+
private final Map<MonitoredSensor, DisplayInfo> mDisplayedSensors =
new HashMap<SensorsHandler.MonitoredSensor, SensorActivity.DisplayInfo>();
+ private final android.os.Handler mUiHandler = new android.os.Handler(this);
/** Called when the activity is first created. */
@Override
@@ -71,6 +73,7 @@ public class SensorActivity extends BaseBindingActivity {
@Override
protected void onResume() {
+ if (DEBUG) Log.d(TAG, "onResume");
// BaseBindingActivity.onResume will bind to the service.
super.onResume();
updateError();
@@ -78,12 +81,14 @@ public class SensorActivity extends BaseBindingActivity {
@Override
protected void onPause() {
+ if (DEBUG) Log.d(TAG, "onPause");
// BaseBindingActivity.onResume will unbind from (but not stop) the service.
super.onPause();
}
@Override
protected void onDestroy() {
+ if (DEBUG) Log.d(TAG, "onDestroy");
super.onDestroy();
removeSensorUi();
}
@@ -92,11 +97,13 @@ public class SensorActivity extends BaseBindingActivity {
@Override
protected void onServiceConnected() {
+ if (DEBUG) Log.d(TAG, "onServiceConnected");
createSensorUi();
}
@Override
protected void onServiceDisconnected() {
+ if (DEBUG) Log.d(TAG, "onServiceDisconnected");
removeSensorUi();
}
@@ -141,7 +148,7 @@ public class SensorActivity extends BaseBindingActivity {
mSensorHandler = (SensorsHandler) getServiceBinder().getHandler(HandlerType.Sensor);
if (mSensorHandler != null) {
- mSensorHandler.addUiListener(mUiListener);
+ mSensorHandler.addUiHandler(mUiHandler);
assert mDisplayedSensors.isEmpty();
List<MonitoredSensor> sensors = mSensorHandler.getSensors();
@@ -156,9 +163,11 @@ public class SensorActivity extends BaseBindingActivity {
}
private void removeSensorUi() {
+ if (mSensorHandler != null) {
+ mSensorHandler.removeUiHandler(mUiHandler);
+ mSensorHandler = null;
+ }
mTableLayout.removeAllViews();
- mSensorHandler.removeUiListener(mUiListener);
- mSensorHandler = null;
for (DisplayInfo info : mDisplayedSensors.values()) {
info.release();
}
@@ -219,33 +228,29 @@ public class SensorActivity extends BaseBindingActivity {
}
}
- private class OurUiListener implements UiListener {
- @Override
- public void onHandlerEvent(final int event, final Object... params) {
- // This is invoked from the emulator connection thread.
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- DisplayInfo info = null;
- switch(event) {
- case SensorsHandler.SENSOR_STATE_CHANGED:
- info = mDisplayedSensors.get(params[0]);
- if (info != null) {
- info.updateState();
- }
- break;
- case SensorsHandler.SENSOR_DISPLAY_MODIFIED:
- info = mDisplayedSensors.get(params[0]);
- if (info != null) {
- info.updateValue();
- }
- updateStatus(Integer.toString(mSensorHandler.getEventSentCount()) +
- " events sent");
- break;
- }
- }
- });
+ /** Implementation of Handler.Callback */
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (DEBUG) Log.d(TAG, "handleMessage" + msg.toString());
+ DisplayInfo info = null;
+ switch (msg.what) {
+ case SensorsHandler.SENSOR_STATE_CHANGED:
+ info = mDisplayedSensors.get(msg.obj);
+ if (info != null) {
+ info.updateState();
+ }
+ break;
+ case SensorsHandler.SENSOR_DISPLAY_MODIFIED:
+ info = mDisplayedSensors.get(msg.obj);
+ if (info != null) {
+ info.updateValue();
+ }
+ if (mSensorHandler != null) {
+ updateStatus(Integer.toString(mSensorHandler.getEventSentCount()) + " events sent");
+ }
+ break;
}
+ return true; // we consumed this message
}
private void updateStatus(String status) {
@@ -255,7 +260,7 @@ public class SensorActivity extends BaseBindingActivity {
private void updateError() {
ControllerBinder binder = getServiceBinder();
- String error = binder == null ? "" : binder.getSensorErrors();
+ String error = binder == null ? "" : binder.getServiceError();
if (error == null) {
error = "";
}
diff --git a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java
index a89eefd..0e30c4d 100755
--- a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java
@@ -18,8 +18,13 @@ 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;
@@ -31,9 +36,35 @@ import com.android.tools.sdkcontroller.service.ControllerService;
* <p/>
* The {@link ControllerService} can deal with several handlers, each have a specific
* purpose as described by {@link HandlerType}.
+ * <p/>
+ * 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.
+ * <p/>
+ * The {@link BaseHandler} keeps track of the current {@link EmulatorConnection} given
+ * via {@link #onStart(EmulatorConnection, Context)}.
+ * <p/>
+ * 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<String> mEventQueue = new LinkedBlockingQueue<String>();
+ private static String EVENT_QUEUE_END = "@end@";
+ private final List<android.os.Handler> mUiHandlers = new ArrayList<android.os.Handler>();
+ private final HandlerType mHandlerType;
+ private final Thread mEventThread;
+ private int mPort;
+
/**
* The type of action that this handler manages.
*/
@@ -46,13 +77,48 @@ public abstract class BaseHandler {
}
/**
- * Returns the type of this handler.
+ * 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 abstract HandlerType getType();
+ public HandlerType getType() {
+ return mHandlerType;
+ }
/**
- * The communication port used by this handler to communicate with the emulator.
+ * Returns he communication port used by this handler to communicate with the emulator,
+ * as given to the constructor.
* <p/>
* 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
@@ -60,13 +126,28 @@ public abstract class BaseHandler {
*
* @return A non-null port value.
*/
- public abstract int getPort();
+ 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.
* <p/>
* Note that this will <em>not</em> be called if the {@link EmulatorConnection}
* fails to bind to the underlying socket.
+ * <p/>
+ * 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
@@ -74,13 +155,45 @@ public abstract class BaseHandler {
* need to be particularly careful in <em>not</em> sending network data
* from the main UI thread.
* @param context The controller service' context.
+ * @see #getConnection()
*/
- public abstract void onStart(EmulatorConnection connection, Context context);
+ 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 abstract void onStop();
+ 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
@@ -103,51 +216,61 @@ public abstract class BaseHandler {
// Interaction from handler towards listening UI
/**
- * Interface to be implemented by activities that use this handler.
- * This is used by the handler to send event data to any 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 interface UiListener {
- public void onHandlerEvent(int event, Object...params);
+ public boolean hasUiHandler(android.os.Handler uiHandler) {
+ return uiHandler != null && mUiHandlers.contains(uiHandler);
}
- private final List<UiListener> mUiListeners = new ArrayList<UiListener>();
-
/**
- * Registers a new UI listener.
+ * Registers a new UI handler.
*
- * @param listener A non-null UI listener to register.
- * Ignored if the listener is null or already registered.
+ * @param uiHandler A non-null UI handler to register.
+ * Ignored if the UI handler is null or already registered.
*/
- public void addUiListener(UiListener listener) {
- assert listener != null;
- if (listener != null) {
- if (!mUiListeners.contains(listener)) {
- mUiListeners.add(listener);
+ public void addUiHandler(android.os.Handler uiHandler) {
+ assert uiHandler != null;
+ if (uiHandler != null) {
+ if (!mUiHandlers.contains(uiHandler)) {
+ mUiHandlers.add(uiHandler);
}
}
}
-
/**
- * Unregisters an UI listener.
+ * Unregisters an UI handler.
*
- * @param listener A non-null UI listener to unregister.
+ * @param uiHandler A non-null UI listener to unregister.
* Ignored if the listener is null or already registered.
*/
- public void removeUiListener(UiListener listener) {
- assert listener != null;
- mUiListeners.remove(listener);
+ 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 any listening UI.
+ * Protected method to be used by handlers to send an event to all UI handlers.
*
- * @param event The event code. To be defined by the handler itself.
- * @param params Any parameters that the handler defines for this event.
+ * @param msg An event with parameters. To be defined by the handler itself.
*/
- protected void notifyUi(int event, Object...params) {
- for (UiListener listener : mUiListeners) {
- listener.onHandlerEvent(event, params);
+ protected void notifyUiHandlers(Message msg) {
+ for (android.os.Handler uiHandler : mUiHandlers) {
+ uiHandler.sendMessage(msg);
}
}
diff --git a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/MultiTouchHandler.java b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/MultiTouchHandler.java
new file mode 100755
index 0000000..6f64485
--- /dev/null
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/MultiTouchHandler.java
@@ -0,0 +1,121 @@
+/*
+ * 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 android.content.Context;
+import android.graphics.Point;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.tools.sdkcontroller.lib.EmulatorConnection;
+
+
+public class MultiTouchHandler extends BaseHandler {
+
+ @SuppressWarnings("hiding")
+ private static final String TAG = MultiTouchHandler.class.getSimpleName();
+ /**
+ * A new frame buffer has been received from the emulator.
+ * Parameter {@code obj} is a {@code byte[] array} containing the screen data.
+ */
+ public static final int EVENT_FRAME_BUFFER = 1;
+ /**
+ * A multi-touch "start" command has been received from the emulator.
+ * Parameter {@code obj} is the string parameter from the start command.
+ */
+ public static final int EVENT_MT_START = 2;
+ /**
+ * A multi-touch "stop" command has been received from the emulator.
+ * There is no {@code obj} parameter associated.
+ */
+ public static final int EVENT_MT_STOP = 3;
+
+ private static final Point mViewSize = new Point(0, 0);
+
+ public MultiTouchHandler() {
+ super(HandlerType.MultiTouch, EmulatorConnection.MULTITOUCH_PORT);
+ }
+
+ public void setViewSize(int width, int height) {
+ mViewSize.set(width, height);
+ }
+
+ @Override
+ public void onStart(EmulatorConnection connection, Context context) {
+ super.onStart(connection, context);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ }
+
+ /**
+ * Called when a query is received from the emulator. NOTE: This method is
+ * called from the I/O loop.
+ *
+ * @param query Name of the query received from the emulator. The allowed
+ * queries are: - 'start' - Starts delivering touch screen events
+ * to the emulator. - 'stop' - Stops delivering touch screen
+ * events to the emulator.
+ * @param param Query parameters.
+ * @return Zero-terminated reply string. String must be formatted as such:
+ * "ok|ko[:reply data]"
+ */
+ @Override
+ public String onEmulatorQuery(String query, String param) {
+ if (query.contentEquals("start")) {
+ Message msg = Message.obtain();
+ msg.what = EVENT_MT_START;
+ msg.obj = param;
+ notifyUiHandlers(msg);
+ return "ok:" + mViewSize.x + "x" + mViewSize.y + "\0";
+
+ } else if (query.contentEquals("stop")) {
+ notifyUiHandlers(EVENT_MT_STOP);
+ return "ok\0";
+
+ } else {
+ Log.e(TAG, "Unknown query " + query + "(" + param + ")");
+ return "ko:Unknown query\0";
+ }
+ }
+
+ /**
+ * Called when a BLOB query is received from the emulator.
+ * <p/>
+ * This query is used to deliver framebuffer updates in the emulator. The
+ * blob contains an update header, followed by the bitmap containing updated
+ * rectangle. The header is defined as MTFrameHeader structure in
+ * external/qemu/android/multitouch-port.h
+ * <p/>
+ * NOTE: This method is called from the I/O loop, so all communication with
+ * the emulator will be "on hold" until this method returns.
+ *
+ * @param array contains BLOB data for the query.
+ * @return Empty string: this query doesn't require any response.
+ */
+ @Override
+ public String onEmulatorBlobQuery(byte[] array) {
+ Message msg = Message.obtain();
+ msg.what = EVENT_FRAME_BUFFER;
+ msg.obj = array;
+ notifyUiHandlers(msg);
+ return "";
+ }
+
+}
diff --git a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/MultitouchHandler.java b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/MultitouchHandler.java
deleted file mode 100755
index a6032c9..0000000
--- a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/MultitouchHandler.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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 android.content.Context;
-
-import com.android.tools.sdkcontroller.lib.EmulatorConnection;
-
-
-public class MultitouchHandler extends BaseHandler {
-
- @Override
- public HandlerType getType() {
- return HandlerType.MultiTouch;
- }
-
- @Override
- public int getPort() {
- return EmulatorConnection.MULTITOUCH_PORT;
- }
-
- @Override
- public void onStart(EmulatorConnection connection, Context context) {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void onStop() {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public String onEmulatorQuery(String query, String param) {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public String onEmulatorBlobQuery(byte[] array) {
- // TODO Auto-generated method stub
- return null;
- }
-
-}
diff --git a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/SensorsHandler.java b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/SensorsHandler.java
index d093e04..f23e38a 100755
--- a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/SensorsHandler.java
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/SensorsHandler.java
@@ -18,15 +18,13 @@ 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.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.os.Message;
import android.util.Log;
import com.android.tools.sdkcontroller.lib.EmulatorConnection;
@@ -34,41 +32,28 @@ import com.android.tools.sdkcontroller.lib.EmulatorConnection;
public class SensorsHandler extends BaseHandler {
+ @SuppressWarnings("hiding")
private static String TAG = SensorsHandler.class.getSimpleName();
+ @SuppressWarnings("hiding")
private static boolean DEBUG = true;
- private static String EVENT_QUEUE_END = "@end@";
/**
* Sensor "enabled by emulator" state has changed.
- * Parameters are [0]=MonitoredSensor, [1]=Boolean isEnabledByEmulator.
+ * Parameter {@code obj} is the {@link MonitoredSensor}.
*/
public static final int SENSOR_STATE_CHANGED = 1;
/**
* Sensor display value has changed.
- * Parameters are [0]=MonitoredSensor, [1]=String value
+ * Parameter {@code obj} is the {@link MonitoredSensor}.
*/
public static final int SENSOR_DISPLAY_MODIFIED = 2;
/** Array containing monitored sensors. */
private final List<MonitoredSensor> mSensors = new ArrayList<MonitoredSensor>();
- private EmulatorConnection mConnection;
private SensorManager mSenMan;
- private final AtomicInteger mEventCount = new AtomicInteger(0);
- private volatile boolean mRunEventQueue = true;
- private final BlockingQueue<String> mEventQueue = new LinkedBlockingQueue<String>();
-
public SensorsHandler() {
- }
-
- @Override
- public HandlerType getType() {
- return HandlerType.Sensor;
- }
-
- @Override
- public int getPort() {
- return EmulatorConnection.SENSORS_PORT;
+ super(HandlerType.Sensor, EmulatorConnection.SENSORS_PORT);
}
/**
@@ -81,16 +66,9 @@ public class SensorsHandler extends BaseHandler {
return mSensors;
}
- public int getEventSentCount() {
- return mEventCount.get();
- }
-
@Override
public void onStart(EmulatorConnection connection, Context context) {
- assert connection != null;
- mConnection = connection;
- mRunEventQueue = true;
- mEventThread.start();
+ super.onStart(connection, context);
// Iterate through the available sensors, adding them to the array.
SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
@@ -119,12 +97,7 @@ public class SensorsHandler extends BaseHandler {
@Override
public void onStop() {
stopSensors();
- // Stop the message queue
- mConnection = null;
- if (mRunEventQueue) {
- mRunEventQueue = false;
- mEventQueue.offer(EVENT_QUEUE_END);
- }
+ super.onStop();
}
/**
@@ -275,38 +248,6 @@ public class SensorsHandler extends BaseHandler {
* Internals
**************************************************************************/
- private final Thread mEventThread = new Thread(new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "EventThread.started");
- 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", e);
- }
- }
- if (DEBUG) Log.d(TAG, "EventThread.terminate");
- }
- }, "eventThread");
-
- /**
- * Sends sensor's event to the emulator.
- *
- * @param msg Sensor's event message.
- */
- private void sendSensorEvent(String msg) {
- try {
- mEventQueue.put(msg);
- } catch (InterruptedException e) {
- Log.e(TAG, "NotifQueue.put", e);
- }
- }
-
/**
* Start listening to all monitored sensors.
*/
@@ -570,7 +511,11 @@ public class SensorsHandler extends BaseHandler {
if (DEBUG) Log.d(TAG, ">>> Sensor " + getEmulatorFriendlyName() + " is enabled.");
mEnabledByEmulator = true;
mValue = "";
- notifyUi(SENSOR_STATE_CHANGED, this, mEnabledByEmulator);
+
+ Message msg = Message.obtain();
+ msg.what = SENSOR_STATE_CHANGED;
+ msg.obj = this;
+ notifyUiHandlers(msg);
}
/**
@@ -578,10 +523,14 @@ public class SensorsHandler extends BaseHandler {
* the UI thread.
*/
private void disableSensor() {
- Log.e(TAG, "<<< Sensor " + getEmulatorFriendlyName() + " is disabled.");
+ if (DEBUG) Log.w(TAG, "<<< Sensor " + getEmulatorFriendlyName() + " is disabled.");
mEnabledByEmulator = false;
mValue = "Disabled by emulator";
- notifyUi(SENSOR_STATE_CHANGED, this, mEnabledByEmulator);
+
+ Message msg = Message.obtain();
+ msg.what = SENSOR_STATE_CHANGED;
+ msg.obj = this;
+ notifyUiHandlers(msg);
}
private class OurSensorEventListener implements SensorEventListener {
@@ -594,26 +543,29 @@ public class SensorsHandler extends BaseHandler {
// Display current sensor value, and format message that will be
// sent to the emulator.
final int nArgs = event.values.length;
- String msg;
+ String str;
String val;
if (nArgs == 3) {
val = String.format(mTextFmt, event.values[0], event.values[1],event.values[2]);
- msg = String.format(mMsgFmt, event.values[0], event.values[1], event.values[2]);
+ str = String.format(mMsgFmt, event.values[0], event.values[1], event.values[2]);
} else if (nArgs == 2) {
val = String.format(mTextFmt, event.values[0], event.values[1]);
- msg = String.format(mMsgFmt, event.values[0], event.values[1]);
+ str = String.format(mMsgFmt, event.values[0], event.values[1]);
} else if (nArgs == 1) {
val = String.format(mTextFmt, event.values[0]);
- msg = String.format(mMsgFmt, event.values[0]);
+ str = String.format(mMsgFmt, event.values[0]);
} else {
Log.e(TAG, "Unexpected number of values " + event.values.length
+ " in onSensorChanged for sensor " + mSensor.getName());
return;
}
mValue = val;
- sendSensorEvent(msg);
+ sendEventToEmulator(str);
- notifyUi(SENSOR_DISPLAY_MODIFIED, this, val);
+ Message msg = Message.obtain();
+ msg.what = SENSOR_DISPLAY_MODIFIED;
+ msg.obj = this;
+ notifyUiHandlers(msg);
}
/**
diff --git a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/service/ControllerService.java b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/service/ControllerService.java
index 9390d07..cd35833 100755
--- a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/service/ControllerService.java
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/service/ControllerService.java
@@ -35,7 +35,7 @@ import com.android.tools.sdkcontroller.R;
import com.android.tools.sdkcontroller.activities.MainActivity;
import com.android.tools.sdkcontroller.handlers.BaseHandler;
import com.android.tools.sdkcontroller.handlers.BaseHandler.HandlerType;
-import com.android.tools.sdkcontroller.handlers.MultitouchHandler;
+import com.android.tools.sdkcontroller.handlers.MultiTouchHandler;
import com.android.tools.sdkcontroller.handlers.SensorsHandler;
import com.android.tools.sdkcontroller.lib.EmulatorConnection;
import com.android.tools.sdkcontroller.lib.EmulatorConnection.EmulatorConnectionType;
@@ -47,7 +47,7 @@ import com.android.tools.sdkcontroller.lib.EmulatorListener;
* <p/>
* The service manages a number of action "handlers" which can be seen as individual tasks
* that the user might want to accomplish, for example "sending sensor data to the emulator"
- * or "sending multitouch data and displaying an emulator screen".
+ * or "sending multi-touch data and displaying an emulator screen".
* <p/>
* Each handler currently has its own emulator connection associated to it (cf class
* {@code EmuCnxHandler} below. However our goal is to later move to a single connection channel
@@ -58,7 +58,7 @@ import com.android.tools.sdkcontroller.lib.EmulatorListener;
* to deal with these specific details. <br/>
* For example the {@link SensorsHandler} initializes its sensor list as soon as created
* and then tries to send data as soon as there's an emulator connection.
- * On the other hand the {@link MultitouchHandler} lays dormant till there's an UI interacting
+ * On the other hand the {@link MultiTouchHandler} lays dormant till there's an UI interacting
* with it.
*/
public class ControllerService extends Service {
@@ -84,7 +84,7 @@ public class ControllerService extends Service {
private static volatile boolean gServiceIsRunning = false;
/** Internal error reported by the service. */
- private String mSensorError = "";
+ private String mServiceError = "";
private final Set<EmuCnxHandler> mHandlers = new HashSet<ControllerService.EmuCnxHandler>();
@@ -116,7 +116,7 @@ public class ControllerService extends Service {
*
* @param listener A non-null listener. Ignored if already listed.
*/
- public void addListener(ControllerListener listener) {
+ public void addControllerListener(ControllerListener listener) {
assert listener != null;
if (listener != null) {
synchronized(mListeners) {
@@ -132,17 +132,28 @@ public class ControllerService extends Service {
*
* @param listener A listener to remove. Can be null.
*/
- public void removeListener(ControllerListener listener) {
+ public void removeControllerListener(ControllerListener listener) {
assert listener != null;
synchronized(mListeners) {
mListeners.remove(listener);
}
}
- public String getSensorErrors() {
- return mSensorError;
+ /**
+ * Returns the error string accumulated by the service.
+ * Typically these would relate to failures to establish the communication
+ * channel(s) with the emulator, or unexpected disconnections.
+ */
+ public String getServiceError() {
+ return mServiceError;
}
+ /**
+ * Indicates when <em>all</all> the communication channels for all handlers
+ * are properly connected.
+ *
+ * @return True if all the handler's communication channels are connected.
+ */
public boolean isEmuConnected() {
for (EmuCnxHandler handler : mHandlers) {
if (!handler.isConnected()) {
@@ -152,6 +163,12 @@ public class ControllerService extends Service {
return true;
}
+ /**
+ * Returns the handler for the given type.
+ *
+ * @param type One of the {@link HandlerType}s. Must not be null.
+ * @return Null if the type is not found, otherwise the handler's unique instance.
+ */
public BaseHandler getHandler(HandlerType type) {
for (EmuCnxHandler handler : mHandlers) {
BaseHandler h = handler.getHandler();
@@ -235,11 +252,13 @@ public class ControllerService extends Service {
@Override
public String onEmulatorQuery(String query, String param) {
+ if (DEBUG) Log.d(TAG, mHandler.getType().toString() + " Query " + query);
return mHandler.onEmulatorQuery(query, param);
}
@Override
public String onEmulatorBlobQuery(byte[] array) {
+ if (DEBUG) Log.d(TAG, mHandler.getType().toString() + " BlobQuery " + array.length);
return mHandler.onEmulatorBlobQuery(array);
}
@@ -256,7 +275,7 @@ public class ControllerService extends Service {
// This will call onEmulatorBindResult with the result.
mCnx.connect(mHandler.getPort(), EmulatorConnectionType.SYNC_CONNECTION);
}
- }, "EmuCnxH.connect");
+ }, "EmuCnxH.connect-" + mHandler.getType().toString());
t.start();
return this;
@@ -306,7 +325,7 @@ public class ControllerService extends Service {
disconnectAll();
assert mHandlers.isEmpty();
- mHandlers.add(new EmuCnxHandler(new MultitouchHandler()).connect());
+ mHandlers.add(new EmuCnxHandler(new MultiTouchHandler()).connect());
mHandlers.add(new EmuCnxHandler(new SensorsHandler()).connect());
} catch (Exception e) {
addError("Connection failed: " + e.toString());
@@ -340,7 +359,7 @@ public class ControllerService extends Service {
* Resets the error string and notify listeners.
*/
private void resetError() {
- mSensorError = "";
+ mServiceError = "";
notifyErrorChanged();
}
@@ -351,10 +370,10 @@ public class ControllerService extends Service {
*/
private void addError(String error) {
Log.e(TAG, error);
- if (mSensorError.length() > 0) {
- mSensorError += "\n";
+ if (mServiceError.length() > 0) {
+ mServiceError += "\n";
}
- mSensorError += error;
+ mServiceError += error;
notifyErrorChanged();
}
diff --git a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/views/MultiTouchView.java b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/views/MultiTouchView.java
new file mode 100755
index 0000000..d612769
--- /dev/null
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/views/MultiTouchView.java
@@ -0,0 +1,231 @@
+/*
+ * 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.views;
+
+import java.io.InputStream;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Implements a main view for the application providing multi-touch emulation.
+ */
+public class MultiTouchView extends View {
+ /** Tag for logging messages. */
+ private static final String TAG = MultiTouchView.class.getSimpleName();
+ /**
+ * Back-end bitmap. Initialized in onSizeChanged(), updated in
+ * onTouchEvent() and drawn in onDraw().
+ */
+ private Bitmap mBitmap;
+ /** Default Paint instance for drawing the bitmap. */
+ private final Paint mPaint = new Paint();
+ /** Canvas instance for this view. */
+ private Canvas mCanvas;
+ /** Emulator screen width to this view width ratio. */
+ private float mDx = 1;
+ /** Emulator screen height to this view height ratio. */
+ private float mDy = 1;
+ /**
+ * Flags whether or not image received from the emulator should be rotated.
+ * Rotation is required when display orientation state of the emulator and
+ * the device doesn't match.
+ */
+ private boolean mRotateDisplay;
+ /** Base matrix that keep emulator->device display scaling */
+ private Matrix mBaseMatrix = new Matrix();
+ /** Matrix that is used to draw emulator's screen on the device. */
+ private Matrix mDrawMatrix = new Matrix();
+
+ /**
+ * Simple constructor to use when creating a view from code.
+ *
+ * @see View#View(Context)
+ */
+ public MultiTouchView(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Constructor that is called when inflating a view from XML.
+ *
+ * @see View#View(Context, AttributeSet)
+ */
+ public MultiTouchView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ /**
+ * Perform inflation from XML and apply a class-specific base style.
+ *
+ * @see View#View(Context, AttributeSet, int)
+ */
+ public MultiTouchView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ // TODO Add constructor-time code here.
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBitmap);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ // Just draw the back-end bitmap without zooming or scaling.
+ if (mBitmap != null) {
+ canvas.drawBitmap(mBitmap, 0, 0, null);
+ }
+ }
+
+ /**
+ * Sets emulator screen width and height to this view width and height
+ * ratio.
+ *
+ * @param dx Emulator screen width to this view width ratio.
+ * @param dy Emulator screen height to this view height ratio.
+ * @param rotateDisplay Flags whether image received from the emulator
+ * should be rotated when drawn on the device.
+ */
+ public void setDxDy(float dx, float dy, boolean rotateDisplay) {
+ mDx = dx;
+ mDy = dy;
+ mRotateDisplay = rotateDisplay;
+
+ mBaseMatrix.setScale(dx, dy);
+ if (mRotateDisplay) {
+ mBaseMatrix.postRotate(90);
+ mBaseMatrix.postTranslate(getWidth(), 0);
+ }
+ }
+
+ /**
+ * Computes draw matrix for the emulator screen update.
+ *
+ * @param x Left screen coordinate of the bitmap on emulator screen.
+ * @param y Top screen coordinate of the bitmap on emulator screen.
+ */
+ private void computeDrawMatrix(int x, int y) {
+ mDrawMatrix.set(mBaseMatrix);
+ if (mRotateDisplay) {
+ mDrawMatrix.postTranslate(-y * mDy, x * mDx);
+ } else {
+ mDrawMatrix.postTranslate(x * mDx, y * mDy);
+ }
+ }
+
+ /**
+ * Draws a bitmap on the screen.
+ *
+ * @param x Left screen coordinate of the bitmap on emulator screen.
+ * @param y Top screen coordinate of the bitmap on emulator screen.
+ * @param w Width of the bitmap on the emulator screen.
+ * @param h Height of the bitmap on the emulator screen.
+ * @param colors Bitmap to draw.
+ */
+ public void drawBitmap(int x, int y, int w, int h, int[] colors) {
+ if (mCanvas != null) {
+ final Bitmap bmp = Bitmap.createBitmap(colors, 0, w, w, h, Bitmap.Config.ARGB_8888);
+
+ computeDrawMatrix(x, y);
+
+ /* Draw the bitmap and invalidate the updated region. */
+ mCanvas.drawBitmap(bmp, mDrawMatrix, mPaint);
+ invalidate();
+ }
+ }
+
+ /**
+ * Draws a JPEG bitmap on the screen.
+ *
+ * @param x Left screen coordinate of the bitmap on emulator screen.
+ * @param y Top screen coordinate of the bitmap on emulator screen.
+ * @param w Width of the bitmap on the emulator screen.
+ * @param h Height of the bitmap on the emulator screen.
+ * @param jpeg JPEG bitmap to draw.
+ */
+ public void drawJpeg(int x, int y, int w, int h, InputStream jpeg) {
+ if (mCanvas != null) {
+ final Bitmap bmp = BitmapFactory.decodeStream(jpeg);
+
+ computeDrawMatrix(x, y);
+
+ /* Draw the bitmap and invalidate the updated region. */
+ mCanvas.drawBitmap(bmp, mDrawMatrix, mPaint);
+ invalidate();
+ }
+ }
+
+ /**
+ * Constructs touch event message to be send to emulator.
+ *
+ * @param sb String builder where to construct the message.
+ * @param event Event for which to construct the message.
+ * @param ptr_index Index of the motion pointer for which to construct the
+ * message.
+ */
+ public void constructEventMessage(StringBuilder sb, MotionEvent event, int ptr_index) {
+ sb.append(" pid=").append(event.getPointerId(ptr_index));
+ if (mRotateDisplay == false) {
+ sb.append(" x=").append((int) (event.getX(ptr_index) / mDx));
+ sb.append(" y=").append((int) (event.getY(ptr_index) / mDy));
+ } else {
+ sb.append(" x=").append((int) (event.getY(ptr_index) / mDy));
+ sb.append(" y=").append((int) (getWidth() - event.getX(ptr_index) / mDx));
+ }
+ // At the system level the input reader takes integers in the range
+ // 0 - 100 for the pressure.
+ int pressure = (int) (event.getPressure(ptr_index) * 100);
+ // Make sure it doesn't exceed 100...
+ if (pressure > 100) {
+ pressure = 100;
+ }
+ sb.append(" pressure=").append(pressure);
+ }
+
+ /***************************************************************************
+ * Logging wrappers
+ **************************************************************************/
+
+ @SuppressWarnings("unused")
+ private void Loge(String log) {
+ Log.e(TAG, log);
+ }
+
+ @SuppressWarnings("unused")
+ private void Logw(String log) {
+ Log.w(TAG, log);
+ }
+
+ @SuppressWarnings("unused")
+ private void Logv(String log) {
+ Log.v(TAG, log);
+ }
+}