summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorRaphael <raphael@google.com>2012-03-14 15:59:14 -0700
committerRaphael <raphael@google.com>2012-03-16 08:48:42 -0700
commitb0d1c0b057da074f912c15fd2eebb11cc2175c1e (patch)
treefe03a4024f6d9830d1520cff4d1b0570afc5eea7 /apps
parent564ecd801263c48f1fe05af0349ff4898c7f45c7 (diff)
downloadreplicant_sdk-b0d1c0b057da074f912c15fd2eebb11cc2175c1e.zip
replicant_sdk-b0d1c0b057da074f912c15fd2eebb11cc2175c1e.tar.gz
replicant_sdk-b0d1c0b057da074f912c15fd2eebb11cc2175c1e.tar.bz2
SdkController service: sensor handler.
This implement sensor control using the service. The service has a number of "handlers" (e.g. multitouch or sensors). The sensor handler is automatically started by the service. The multitouch handler OTOH will just be waiting till the controlling activity connects to it. Change-Id: Ic5f92063916efba2c4f503d2607b58bbbe4e0ccf
Diffstat (limited to 'apps')
-rwxr-xr-xapps/SdkController/SdkControllerApp/AndroidManifest.xml36
-rwxr-xr-xapps/SdkController/SdkControllerApp/project.properties1
-rwxr-xr-xapps/SdkController/SdkControllerApp/res/layout/main.xml2
-rwxr-xr-xapps/SdkController/SdkControllerApp/res/layout/sensor_row.xml23
-rwxr-xr-xapps/SdkController/SdkControllerApp/res/layout/sensors.xml27
-rwxr-xr-xapps/SdkController/SdkControllerApp/res/values/strings.xml13
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/SensorActivity.java34
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/BaseBindingActivity.java111
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MainActivity.java (renamed from apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/MainActivity.java)124
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MultitouchActivity.java (renamed from apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/MultitouchActivity.java)2
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/SensorActivity.java232
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java88
-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.java607
-rwxr-xr-xapps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/service/ControllerService.java (renamed from apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/ControllerService.java)190
-rwxr-xr-xapps/SdkController/SdkControllerLib/src/com/android/tools/sdkcontroller/lib/EmulatorConnection.java16
16 files changed, 1407 insertions, 159 deletions
diff --git a/apps/SdkController/SdkControllerApp/AndroidManifest.xml b/apps/SdkController/SdkControllerApp/AndroidManifest.xml
index 62e498f..43dcdde 100755
--- a/apps/SdkController/SdkControllerApp/AndroidManifest.xml
+++ b/apps/SdkController/SdkControllerApp/AndroidManifest.xml
@@ -3,24 +3,40 @@
package="com.android.tools.sdkcontroller"
android:versionCode="1"
android:versionName="1.0" >
+
+ <uses-sdk
+ android:minSdkVersion="7"
+ android:targetSdkVersion="15" />
- <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="15"/>
+ <uses-permission android:name="android.permission.INTERNET" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
+
<activity
- android:name=".MainActivity"
- android:label="@string/app_name" android:launchMode="singleInstance">
+ android:name=".activities.MainActivity"
+ android:label="@string/app_name"
+ android:launchMode="singleInstance" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
-
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
- </activity>
- <activity android:name=".SensorActivity" android:launchMode="singleInstance"></activity>
- <activity android:name=".MultitouchActivity" android:launchMode="singleInstance" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale" android:screenOrientation="portrait"></activity>
- <service android:icon="@drawable/ic_launcher" android:description="@string/service_description" android:name="ControllerService"></service>
- </application>
+ </activity>
+
+ <activity
+ android:name=".activities.SensorActivity"
+ android:launchMode="singleInstance" />
+
+ <activity
+ android:name=".activities.MultitouchActivity"
+ android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
+ android:launchMode="singleInstance"
+ android:screenOrientation="portrait" />
-</manifest> \ No newline at end of file
+ <service
+ android:name=".service.ControllerService"
+ android:description="@string/service_description"
+ android:icon="@drawable/ic_launcher" />
+ </application>
+</manifest>
diff --git a/apps/SdkController/SdkControllerApp/project.properties b/apps/SdkController/SdkControllerApp/project.properties
index 9c52cb1..3b50fc7 100755
--- a/apps/SdkController/SdkControllerApp/project.properties
+++ b/apps/SdkController/SdkControllerApp/project.properties
@@ -12,3 +12,4 @@
# Project target.
target=android-15
+android.library.reference.1=../SdkControllerLib
diff --git a/apps/SdkController/SdkControllerApp/res/layout/main.xml b/apps/SdkController/SdkControllerApp/res/layout/main.xml
index 612b9fc..3722206 100755
--- a/apps/SdkController/SdkControllerApp/res/layout/main.xml
+++ b/apps/SdkController/SdkControllerApp/res/layout/main.xml
@@ -75,7 +75,7 @@
android:padding="8dp"
android:text="[service errors]"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="#FFF0"
+ android:textColor="#FFF0"
tools:ignore="HardcodedText" />
<TextView
diff --git a/apps/SdkController/SdkControllerApp/res/layout/sensor_row.xml b/apps/SdkController/SdkControllerApp/res/layout/sensor_row.xml
new file mode 100755
index 0000000..ca167a7
--- /dev/null
+++ b/apps/SdkController/SdkControllerApp/res/layout/sensor_row.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" >
+
+ <CheckBox
+ android:id="@+id/row_checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="10dp"
+ android:saveEnabled="false"
+ android:text="Some CheckBox"
+ tools:ignore="HardcodedText" />
+
+ <TextView
+ android:id="@+id/row_textview"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+</TableRow>
diff --git a/apps/SdkController/SdkControllerApp/res/layout/sensors.xml b/apps/SdkController/SdkControllerApp/res/layout/sensors.xml
new file mode 100755
index 0000000..0fb1e93
--- /dev/null
+++ b/apps/SdkController/SdkControllerApp/res/layout/sensors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sensors_top_description"
+ />
+
+ <ScrollView
+ android:id="@+id/scrollView1"
+ android:layout_width="fill_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" >
+
+ <TableLayout
+ android:id="@+id/tableLayout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:saveEnabled="false" />
+
+ </ScrollView>
+
+</LinearLayout> \ No newline at end of file
diff --git a/apps/SdkController/SdkControllerApp/res/values/strings.xml b/apps/SdkController/SdkControllerApp/res/values/strings.xml
index e840166..642d7b8 100755
--- a/apps/SdkController/SdkControllerApp/res/values/strings.xml
+++ b/apps/SdkController/SdkControllerApp/res/values/strings.xml
@@ -20,19 +20,20 @@
<resources>
<!-- Strings for manifest. -->
- <string name="app_name">SdkControllerApp</string>
- <string name="service_description">Background service for SdkController</string>
+ <string name="app_name">Sdk Controller App</string>
+ <string name="service_description">Background service for Sdk Controller App</string>
<!-- Strings for service. -->
<string name="service_notif_title">SdkController is running</string>
-
+
<!-- Strings for layout/main -->
<string name="main_text_intro">(insert a description of the purpose of this app here)</string>
<string name="main_label_service">Service:</string>
<string name="main_label_buttons">What you can do:</string>
<string name="main_btn_open_multitouch">Control Multi-touch</string>
<string name="main_btn_open_sensors">Control Sensors</string>
- <string name="main_service_status_running">Running</string>
- <string name="main_service_status_stopped">Stopped</string>
-
+ <string name="main_service_status_connected">Emulator Connected</string>
+ <string name="main_service_status_disconnected">Emulator Connected</string>
+ <string name="sensors_top_description">Available Sensors:</string>
+
</resources> \ No newline at end of file
diff --git a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/SensorActivity.java b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/SensorActivity.java
deleted file mode 100755
index 64edb01..0000000
--- a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/SensorActivity.java
+++ /dev/null
@@ -1,34 +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;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class SensorActivity extends Activity {
-
- public static String TAG = SensorActivity.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.sensors);
- }
-}
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
new file mode 100755
index 0000000..c94da1e
--- /dev/null
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/BaseBindingActivity.java
@@ -0,0 +1,111 @@
+/*
+ * 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 android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.tools.sdkcontroller.service.ControllerService;
+import com.android.tools.sdkcontroller.service.ControllerService.ControllerBinder;
+import com.android.tools.sdkcontroller.service.ControllerService.ControllerListener;
+
+/**
+ * Base activity class that knows how to bind and unbind from the
+ * {@link ControllerService}.
+ */
+public abstract class BaseBindingActivity extends Activity {
+
+ public static String TAG = BaseBindingActivity.class.getSimpleName();
+ private static boolean DEBUG = true;
+ private ServiceConnection mServiceConnection;
+ private ControllerBinder mServiceBinder;
+
+ public ControllerBinder getServiceBinder() {
+ return mServiceBinder;
+ }
+
+ @Override
+ protected void onResume() {
+ if (DEBUG) Log.d(TAG, "onResume");
+ super.onResume();
+ bindToService();
+ }
+
+ @Override
+ protected void onPause() {
+ if (DEBUG) Log.d(TAG, "onPause");
+ super.onPause();
+ unbindFromService();
+ }
+
+ // ----------
+
+ protected abstract ControllerListener createControllerListener();
+ protected abstract void onServiceConnected();
+ protected abstract void onServiceDisconnected();
+
+ /**
+ * Starts the service and binds to it.
+ */
+ protected void bindToService() {
+ if (mServiceConnection == null) {
+ final ControllerListener listener = createControllerListener();
+
+ mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) Log.d(TAG, "Activity connected to service");
+ mServiceBinder = (ControllerBinder) service;
+ mServiceBinder.addListener(listener);
+ BaseBindingActivity.this.onServiceConnected();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) Log.d(TAG, "Activity disconnected from service");
+ mServiceBinder = null;
+ BaseBindingActivity.this.onServiceDisconnected();
+ }
+ };
+ }
+
+ // Start service so that it doesn't stop when we unbind
+ if (DEBUG) Log.d(TAG, "start requested & bind service");
+ Intent service = new Intent(this, ControllerService.class);
+ startService(service);
+ bindService(service,
+ mServiceConnection,
+ Context.BIND_AUTO_CREATE);
+ }
+
+ /**
+ * Unbinds from the service but does not actually stop the service.
+ * This lets us have it run in the background even if this isn't the active app.
+ */
+ protected void unbindFromService() {
+ if (mServiceConnection != null) {
+ if (DEBUG) Log.d(TAG, "unbind service");
+ unbindService(mServiceConnection);
+ mServiceConnection = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/MainActivity.java b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MainActivity.java
index fb3d223..b221e4f 100755
--- a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/MainActivity.java
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MainActivity.java
@@ -14,15 +14,10 @@
* the License.
*/
-package com.android.tools.sdkcontroller;
+package com.android.tools.sdkcontroller.activities;
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
import android.os.Bundle;
-import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
@@ -32,18 +27,19 @@ import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import android.widget.ToggleButton;
-import com.android.tools.sdkcontroller.ControllerService.ControllerBinder;
-import com.android.tools.sdkcontroller.ControllerService.ControllerListener;
+import com.android.tools.sdkcontroller.R;
+import com.android.tools.sdkcontroller.service.ControllerService;
+import com.android.tools.sdkcontroller.service.ControllerService.ControllerBinder;
+import com.android.tools.sdkcontroller.service.ControllerService.ControllerListener;
-public class MainActivity extends Activity {
+public class MainActivity extends BaseBindingActivity {
+ @SuppressWarnings("hiding")
public static String TAG = MainActivity.class.getSimpleName();
private static boolean DEBUG = true;
private Button mBtnOpenMultitouch;
private Button mBtnOpenSensors;
private ToggleButton mBtnToggleService;
- private ServiceConnection mServiceConnection;
- protected ControllerBinder mServiceBinder;
private TextView mTextError;
private TextView mTextStatus;
@@ -61,34 +57,46 @@ public class MainActivity extends Activity {
@Override
protected void onResume() {
- if (DEBUG) Log.d(TAG, "onResume");
+ // BaseBindingActivity.onResume will bind to the service.
super.onResume();
- bindToService();
updateError();
}
@Override
protected void onPause() {
- if (DEBUG) Log.d(TAG, "onPause");
+ // BaseBindingActivity.onResume will unbind from (but not stop) the service.
super.onPause();
- // On pause we unbind but don't stop -- this is the case when the users goes home
- // or invokes any other activity, including our owns.
- boolean isRunning = mServiceBinder != null;
- unbindFromService();
}
@Override
public void onBackPressed() {
if (DEBUG) Log.d(TAG, "onBackPressed");
- // If back is pressed, we stop the service automatically. It seems more intuitive that way.
+ // If back is pressed, we stop the service automatically.
+ // It seems more intuitive that way.
stopService();
super.onBackPressed();
}
// ----------
- private void setupButtons() {
+ @Override
+ protected void onServiceConnected() {
+ updateButtons();
+ }
+
+ @Override
+ protected void onServiceDisconnected() {
+ updateButtons();
+ }
+
+ @Override
+ protected ControllerListener createControllerListener() {
+ return new MainControllerListener();
+ }
+ // ----------
+
+ private void setupButtons() {
mBtnOpenMultitouch = (Button) findViewById(R.id.btnOpenMultitouch);
mBtnOpenSensors = (Button) findViewById(R.id.btnOpenSensors);
@@ -133,56 +141,6 @@ public class MainActivity extends Activity {
mBtnOpenMultitouch.setEnabled(running);
mBtnOpenSensors.setEnabled(running);
mBtnToggleService.setChecked(running);
-
- mTextStatus.setText(
- getText(running ? R.string.main_service_status_running
- : R.string.main_service_status_stopped));
- }
-
- /**
- * Starts the service and binds to it.
- */
- private void bindToService() {
- if (mServiceConnection == null) {
- final ControllerListener listener = new OurControllerListener();
-
- mServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (DEBUG) Log.d(TAG, "Activity connected to service");
- mServiceBinder = (ControllerBinder) service;
- mServiceBinder.addListener(listener);
- updateButtons();
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- if (DEBUG) Log.d(TAG, "Activity disconnected from service");
- mServiceBinder = null;
- updateButtons();
- }
- };
- }
-
- // Start service so that it doesn't stop when we unbind
- if (DEBUG) Log.d(TAG, "start requested & bind service");
- Intent service = new Intent(this, ControllerService.class);
- startService(service);
- bindService(service,
- mServiceConnection,
- Context.BIND_AUTO_CREATE);
- }
-
- /**
- * Unbinds from the service but does not actually stop the service.
- * This lets us have it run in the background even if this isn't the active app.
- */
- private void unbindFromService() {
- if (mServiceConnection != null) {
- if (DEBUG) Log.d(TAG, "unbind service");
- unbindService(mServiceConnection);
- mServiceConnection = null;
- }
}
/**
@@ -195,9 +153,9 @@ public class MainActivity extends Activity {
stopService(service);
}
- private class OurControllerListener implements ControllerListener {
+ private class MainControllerListener implements ControllerListener {
@Override
- public void onErrorChanged(String error) {
+ public void onErrorChanged() {
runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -205,10 +163,21 @@ public class MainActivity extends Activity {
}
});
}
+
+ @Override
+ public void onStatusChanged() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ updateStatus();
+ }
+ });
+ }
}
private void updateError() {
- String error = mServiceBinder == null ? "" : mServiceBinder.getSensorErrors();
+ ControllerBinder binder = getServiceBinder();
+ String error = binder == null ? "" : binder.getSensorErrors();
if (error == null) {
error = "";
}
@@ -216,4 +185,13 @@ public class MainActivity extends Activity {
mTextError.setVisibility(error.length() == 0 ? View.GONE : View.VISIBLE);
mTextError.setText(error);
}
+
+ private void updateStatus() {
+ ControllerBinder binder = getServiceBinder();
+ boolean connected = binder == null ? false : binder.isEmuConnected();
+ mTextStatus.setText(
+ getText(connected ? R.string.main_service_status_connected
+ : R.string.main_service_status_disconnected));
+
+ }
} \ No newline at end of file
diff --git a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/MultitouchActivity.java b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MultitouchActivity.java
index fd9bf22..0e65482 100755
--- a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/MultitouchActivity.java
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/MultitouchActivity.java
@@ -14,7 +14,7 @@
* the License.
*/
-package com.android.tools.sdkcontroller;
+package com.android.tools.sdkcontroller.activities;
import android.app.Activity;
import android.os.Bundle;
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
new file mode 100755
index 0000000..14a1c72
--- /dev/null
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
@@ -0,0 +1,232 @@
+/*
+ * 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.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+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;
+import com.android.tools.sdkcontroller.service.ControllerService.ControllerListener;
+
+public class SensorActivity extends BaseBindingActivity {
+
+ @SuppressWarnings("hiding")
+ public static String TAG = SensorActivity.class.getSimpleName();
+ @SuppressWarnings("unused")
+ private static boolean DEBUG = true;
+
+ private TableLayout mTableLayout;
+ private SensorsHandler mSensorHandler;
+ private final OurUiListener mUiListener = new OurUiListener();
+ private final Map<MonitoredSensor, DisplayInfo> mDisplayedSensors =
+ new HashMap<SensorsHandler.MonitoredSensor, SensorActivity.DisplayInfo>();
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.sensors);
+ mTableLayout = (TableLayout) findViewById(R.id.tableLayout);
+
+ }
+
+ @Override
+ protected void onResume() {
+ // BaseBindingActivity.onResume will bind to the service.
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ // BaseBindingActivity.onResume will unbind from (but not stop) the service.
+ super.onPause();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ removeSensorUi();
+ }
+
+ // ----------
+
+ @Override
+ protected void onServiceConnected() {
+ createSensorUi();
+ }
+
+ @Override
+ protected void onServiceDisconnected() {
+ removeSensorUi();
+ }
+
+ @Override
+ protected ControllerListener createControllerListener() {
+ return new SensorsControllerListener();
+ }
+
+ // ----------
+
+ private class SensorsControllerListener 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();
+ mTableLayout.setEnabled(binder.isEmuConnected());
+ }
+ });
+ }
+ }
+
+ private void createSensorUi() {
+ final LayoutInflater inflater = getLayoutInflater();
+
+ if (!mDisplayedSensors.isEmpty()) {
+ removeSensorUi();
+ }
+
+ mSensorHandler = (SensorsHandler) getServiceBinder().getHandler(HandlerType.Sensor);
+ if (mSensorHandler != null) {
+ mSensorHandler.addUiListener(mUiListener);
+
+ assert mDisplayedSensors.isEmpty();
+ List<MonitoredSensor> sensors = mSensorHandler.getSensors();
+ for (MonitoredSensor sensor : sensors) {
+ final TableRow row = (TableRow) inflater.inflate(R.layout.sensor_row, mTableLayout, false);
+ mTableLayout.addView(row);
+ mDisplayedSensors.put(sensor, new DisplayInfo(sensor, row));
+ }
+ }
+ }
+
+ private void removeSensorUi() {
+ mTableLayout.removeAllViews();
+ mSensorHandler.removeUiListener(mUiListener);
+ mSensorHandler = null;
+ for (DisplayInfo info : mDisplayedSensors.values()) {
+ info.release();
+ }
+ mDisplayedSensors.clear();
+ }
+
+ private class DisplayInfo implements CompoundButton.OnCheckedChangeListener {
+ private MonitoredSensor mSensor;
+ private CheckBox mChk;
+ private TextView mVal;
+
+ public DisplayInfo(MonitoredSensor sensor, TableRow row) {
+ mSensor = sensor;
+
+ // Initialize displayed checkbox for this sensor, and register
+ // checked state listener for it.
+ mChk = (CheckBox) row.findViewById(R.id.row_checkbox);
+ mChk.setText(sensor.getUiName());
+ mChk.setEnabled(sensor.isEnabledByEmulator());
+ mChk.setChecked(sensor.isEnabledByUser());
+ mChk.setOnCheckedChangeListener(this);
+
+ // Initialize displayed text box for this sensor.
+ mVal = (TextView) row.findViewById(R.id.row_textview);
+ mVal.setText(sensor.getValue());
+ }
+
+ /**
+ * Handles checked state change for the associated CheckBox. If check
+ * box is checked we will register sensor change listener. If it is
+ * unchecked, we will unregister sensor change listener.
+ */
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (mSensor != null) {
+ mSensor.onCheckedChanged(isChecked);
+ }
+ }
+
+ public void release() {
+ mChk = null;
+ mVal = null;
+ mSensor = null;
+
+ }
+
+ public void updateState() {
+ if (mChk != null && mSensor != null) {
+ mChk.setEnabled(mSensor.isEnabledByEmulator());
+ mChk.setChecked(mSensor.isEnabledByUser());
+ }
+ }
+
+ public void updateValue() {
+ if (mVal != null && mSensor != null) {
+ mVal.setText(mSensor.getValue());
+ }
+ }
+ }
+
+ 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();
+ }
+ break;
+ }
+ }
+ });
+ }
+ }
+}
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
new file mode 100755
index 0000000..2db0f43
--- /dev/null
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java
@@ -0,0 +1,88 @@
+/*
+ * 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 android.content.Context;
+
+import com.android.tools.sdkcontroller.lib.EmulatorConnection;
+import com.android.tools.sdkcontroller.lib.EmulatorListener;
+
+
+
+public abstract class BaseHandler {
+
+ public enum HandlerType {
+ MultiTouch,
+ Sensor
+ }
+
+ public abstract HandlerType getType();
+
+ public abstract int getPort();
+
+ public abstract void onStart(EmulatorConnection connection, Context context);
+
+ public abstract void onStop();
+
+ // ------------
+ // Interaction from the emulator connection towards the handler
+
+ /**
+ * Uses the first non-null replies to this query.
+ * @see EmulatorListener#onEmulatorQuery(String, String)
+ */
+ public abstract String onEmulatorQuery(String query, String param);
+
+ /**
+ * Uses the first non-null replies to this query.
+ * @see EmulatorListener#onEmulatorBlobQuery(byte[])
+ */
+ public abstract String onEmulatorBlobQuery(byte[] array);
+
+ // ------------
+ // Interaction from handler towards listening UI
+
+ public interface UiListener {
+ public void onHandlerEvent(int event, Object...params);
+ }
+
+ private final List<UiListener> mUiListeners = new ArrayList<UiListener>();
+
+ public void addUiListener(UiListener listener) {
+ assert listener != null;
+ if (listener != null) {
+ if (!mUiListeners.contains(listener)) {
+ mUiListeners.add(listener);
+ }
+ }
+ }
+
+ public void removeUiListener(UiListener listener) {
+ assert listener != null;
+ mUiListeners.remove(listener);
+ }
+
+ protected void notifyUi(int event, Object...params) {
+ for (UiListener listener : mUiListeners) {
+ listener.onHandlerEvent(event, params);
+ }
+ }
+
+}
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..a6032c9
--- /dev/null
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/MultitouchHandler.java
@@ -0,0 +1,60 @@
+/*
+ * 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
new file mode 100755
index 0000000..f6c3068
--- /dev/null
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/handlers/SensorsHandler.java
@@ -0,0 +1,607 @@
+/*
+ * 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.LinkedList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingDeque;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Log;
+
+import com.android.tools.sdkcontroller.lib.EmulatorConnection;
+
+
+public class SensorsHandler extends BaseHandler {
+
+ private static String TAG = SensorsHandler.class.getSimpleName();
+ private static boolean DEBUG = true;
+
+ /**
+ * Sensor "enabled by emulator" state has changed.
+ * Parameters are [0]=MonitoredSensor, [1]=Boolean isEnabledByEmulator.
+ */
+ public static final int SENSOR_STATE_CHANGED = 1;
+ /**
+ * Sensor display value has changed.
+ * Parameters are [0]=MonitoredSensor, [1]=String value.
+ */
+ 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;
+
+ public SensorsHandler() {
+ }
+
+ @Override
+ public HandlerType getType() {
+ return HandlerType.Sensor;
+ }
+
+ @Override
+ public int getPort() {
+ return EmulatorConnection.SENSORS_PORT;
+ }
+
+ public List<MonitoredSensor> getSensors() {
+ return mSensors;
+ }
+
+ @Override
+ public void onStart(EmulatorConnection connection, Context context) {
+ mConnection = connection;
+ mNotificationThread.start();
+
+ // Iterate through the available sensors, adding them to the array.
+ SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ mSenMan = sm;
+ List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_ALL);
+ int cur_index = 0;
+ for (int n = 0; n < sensors.size(); n++) {
+ Sensor avail_sensor = sensors.get(n);
+
+ // There can be multiple sensors of the same type. We need only one.
+ if (!isSensorTypeAlreadyMonitored(avail_sensor.getType())) {
+ // The first sensor we've got for the given type is not
+ // necessarily the right one. So, use the default sensor
+ // for the given type.
+ Sensor def_sens = sm.getDefaultSensor(avail_sensor.getType());
+ MonitoredSensor to_add = new MonitoredSensor(def_sens);
+ cur_index++;
+ mSensors.add(to_add);
+ if (DEBUG) Log.d(TAG, String.format(
+ "Monitoring sensor #%02d: Name = '%s', Type = 0x%x",
+ cur_index, def_sens.getName(), def_sens.getType()));
+ }
+ }
+ }
+
+ @Override
+ public void onStop() {
+ stopSensors();
+ // To stop the notification queue, we nullify the connect and send a msg
+ mConnection = null;
+ mNotificationQueue.offer("<end>");
+ }
+
+ /**
+ * 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: 'list' - Lists sensors that are monitored by this
+ * application. The application replies to this command with a
+ * string: 'List:<name1>\n<name2>\n...<nameN>\n\0" 'start' -
+ * Starts monitoring sensors. There is no reply for this command.
+ * 'stop' - Stops monitoring sensors. There is no reply for this
+ * command. 'enable:<sensor|all> - Enables notifications for a
+ * sensor / all sensors. 'disable:<sensor|all> - Disables
+ * notifications for a sensor / all sensors.
+ * @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("list")) {
+ return onQueryList();
+ } else if (query.contentEquals("start")) {
+ return onQueryStart();
+ } else if (query.contentEquals("stop")) {
+ return onQueryStop();
+ } else if (query.contentEquals("enable")) {
+ return onQueryEnable(param);
+ } else if (query.contentEquals("disable")) {
+ return onQueryDisable(param);
+ } else {
+ Log.e(TAG, "Unknown query " + query + "(" + param + ")");
+ return "ko:Query is unknown\0";
+ }
+ }
+
+ /**
+ * Called when a BLOB query is received from the emulator. 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 Zero-terminated reply string. String must be formatted as such:
+ * "ok|ko[:reply data]"
+ */
+ @Override
+ public String onEmulatorBlobQuery(byte[] array) {
+ return "ko:Unexpected\0";
+ }
+
+ /***************************************************************************
+ * Query handlers
+ **************************************************************************/
+
+ /**
+ * Handles 'list' query.
+ *
+ * @return List of emulator-friendly names for sensors that are available on
+ * the device.
+ */
+ private String onQueryList() {
+ // List monitored sensors.
+ String list = "ok:";
+ for (MonitoredSensor sensor : mSensors) {
+ list += sensor.getEmulatorFriendlyName();
+ list += "\n";
+ }
+ list += '\0'; // Response must end with zero-terminator.
+ return list;
+ }
+
+ /**
+ * Handles 'start' query.
+ *
+ * @return Empty string. This is a "command" query that doesn't assume any
+ * response.
+ */
+ private String onQueryStart() {
+ startSensors();
+ return "ok\0";
+ }
+
+ /**
+ * Handles 'stop' query.
+ *
+ * @return Empty string. This is a "command" query that doesn't assume any
+ * response.
+ */
+ private String onQueryStop() {
+ stopSensors();
+ return "ok\0";
+ }
+
+ /**
+ * Handles 'enable' query.
+ *
+ * @param param Sensor selector: - all Enables all available sensors, or -
+ * <name> Emulator-friendly name of a sensor to enable.
+ * @return "ok" / "ko": success / failure.
+ */
+ private String onQueryEnable(String param) {
+ if (param.contentEquals("all")) {
+ // Enable all sensors.
+ for (MonitoredSensor sensor : mSensors) {
+ sensor.enableSensor();
+ }
+ return "ok\0";
+ }
+
+ // Lookup sensor by emulator-friendly name.
+ MonitoredSensor sensor = getSensorByEFN(param);
+ if (sensor != null) {
+ sensor.enableSensor();
+ return "ok\0";
+ } else {
+ return "ko:Sensor not found\0";
+ }
+ }
+
+ /**
+ * Handles 'disable' query.
+ *
+ * @param param Sensor selector: - all Disables all available sensors, or -
+ * <name> Emulator-friendly name of a sensor to disable.
+ * @return "ok" / "ko": success / failure.
+ */
+ private String onQueryDisable(String param) {
+ if (param.contentEquals("all")) {
+ // Disable all sensors.
+ for (MonitoredSensor sensor : mSensors) {
+ sensor.disableSensor();
+ }
+ return "ok\0";
+ }
+
+ // Lookup sensor by emulator-friendly name.
+ MonitoredSensor sensor = getSensorByEFN(param);
+ if (sensor != null) {
+ sensor.disableSensor();
+ return "ok\0";
+ } else {
+ return "ko:Sensor not found\0";
+ }
+ }
+
+ /***************************************************************************
+ * Internals
+ **************************************************************************/
+
+ private final BlockingQueue<String> mNotificationQueue = new LinkedBlockingDeque<String>();
+ private final Thread mNotificationThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while(true) {
+ try {
+ String msg = mNotificationQueue.take();
+ if (mConnection == null) {
+ return;
+ }
+ mConnection.sendNotification(msg);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "NotifQueue.take", e);
+ }
+ }
+ }
+ }, "sensorNotif");
+
+ /**
+ * Sends sensor's event to the emulator.
+ *
+ * @param msg Sensor's event message.
+ */
+ private void sendSensorEvent(String msg) {
+ try {
+ mNotificationQueue.put(msg);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "NotifQueue.put", e);
+ }
+ }
+
+ /**
+ * Start listening to all monitored sensors.
+ */
+ private void startSensors() {
+ for (MonitoredSensor sensor : mSensors) {
+ sensor.startListening();
+ }
+ }
+
+ /**
+ * Stop listening to all monitored sensors.
+ */
+ private void stopSensors() {
+ for (MonitoredSensor sensor : mSensors) {
+ sensor.stopListening();
+ }
+ }
+
+ /**
+ * Checks if a sensor for the given type is already monitored.
+ *
+ * @param type Sensor type (one of the Sensor.TYPE_XXX constants)
+ * @return true if a sensor for the given type is already monitored, or
+ * false if the sensor is not monitored.
+ */
+ private boolean isSensorTypeAlreadyMonitored(int type) {
+ for (MonitoredSensor sensor : mSensors) {
+ if (sensor.getType() == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Looks up a monitored sensor by its emulator-friendly name.
+ *
+ * @param name Emulator-friendly name to look up the monitored sensor for.
+ * @return Monitored sensor for the fiven name, or null if sensor was not
+ * found.
+ */
+ private MonitoredSensor getSensorByEFN(String name) {
+ for (MonitoredSensor sensor : mSensors) {
+ if (sensor.mEmulatorFriendlyName.contentEquals(name)) {
+ return sensor;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Encapsulates a sensor that is being monitored. To monitor sensor changes
+ * each monitored sensor registers with sensor manager as a sensor listener.
+ * To control sensor monitoring from the UI, each monitored sensor has two
+ * UI controls associated with it: - A check box (named after sensor) that
+ * can be used to enable, or disable listening to the sensor changes. - A
+ * text view where current sensor value is displayed.
+ */
+ public class MonitoredSensor {
+ /** Sensor to monitor. */
+ private final Sensor mSensor;
+ /** The sensor name to display in the UI. */
+ private String mUiName = "";
+ /** Text view displaying the value of the sensor. */
+ private String mValue = "";
+ /** Emulator-friendly name for the sensor. */
+ private String mEmulatorFriendlyName;
+ /** Formats string to show in the TextView. */
+ private String mTextFmt;
+ /** Formats string to send to the emulator. */
+ private String mMsgFmt;
+ /**
+ * Enabled state. This state is controlled by the emulator, that
+ * maintains its own list of sensors. So, if a sensor is missing, or is
+ * disabled in the emulator, it should be disabled in this application.
+ */
+ private boolean mEnabledByEmulator = false;
+ /** User-controlled enabled state. */
+ private boolean mEnabledByUser = true;
+ private final OurSensorEventListener mListener = new OurSensorEventListener();
+
+ /**
+ * Constructs MonitoredSensor instance, and register the listeners.
+ *
+ * @param sensor Sensor to monitor.
+ */
+ MonitoredSensor(Sensor sensor) {
+ mSensor = sensor;
+ mEnabledByUser = true;
+
+ // Set appropriate sensor name depending on the type. Unfortunately,
+ // we can't really use sensor.getName() here, since the value it
+ // returns (although resembles the purpose) is a bit vaguer than it
+ // should be. Also choose an appropriate format for the strings that
+ // display sensor's value, and strings that are sent to the
+ // emulator.
+ switch (sensor.getType()) {
+ case Sensor.TYPE_ACCELEROMETER:
+ mUiName = "Accelerometer";
+ // 3 floats.
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "acceleration";
+ mMsgFmt = mEmulatorFriendlyName + ":%g:%g:%g\0";
+ break;
+ case 9: // Sensor.TYPE_GRAVITY is missing in API 7
+ // 3 floats.
+ mUiName = "Gravity";
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "gravity";
+ mMsgFmt = mEmulatorFriendlyName + ":%g:%g:%g\0";
+ break;
+ case Sensor.TYPE_GYROSCOPE:
+ mUiName = "Gyroscope";
+ // 3 floats.
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "gyroscope";
+ mMsgFmt = mEmulatorFriendlyName + ":%g:%g:%g\0";
+ break;
+ case Sensor.TYPE_LIGHT:
+ mUiName = "Light";
+ // 1 integer.
+ mTextFmt = "%.0f";
+ mEmulatorFriendlyName = "light";
+ mMsgFmt = mEmulatorFriendlyName + ":%g\0";
+ break;
+ case 10: // Sensor.TYPE_LINEAR_ACCELERATION is missing in API 7
+ mUiName = "Linear acceleration";
+ // 3 floats.
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "linear-acceleration";
+ mMsgFmt = mEmulatorFriendlyName + ":%g:%g:%g\0";
+ break;
+ case Sensor.TYPE_MAGNETIC_FIELD:
+ mUiName = "Magnetic field";
+ // 3 floats.
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "magnetic-field";
+ mMsgFmt = mEmulatorFriendlyName + ":%g:%g:%g\0";
+ break;
+ case Sensor.TYPE_ORIENTATION:
+ mUiName = "Orientation";
+ // 3 integers.
+ mTextFmt = "%+03.0f %+03.0f %+03.0f";
+ mEmulatorFriendlyName = "orientation";
+ mMsgFmt = mEmulatorFriendlyName + ":%g:%g:%g\0";
+ break;
+ case Sensor.TYPE_PRESSURE:
+ mUiName = "Pressure";
+ // 1 integer.
+ mTextFmt = "%.0f";
+ mEmulatorFriendlyName = "pressure";
+ mMsgFmt = mEmulatorFriendlyName + ":%g\0";
+ break;
+ case Sensor.TYPE_PROXIMITY:
+ mUiName = "Proximity";
+ // 1 integer.
+ mTextFmt = "%.0f";
+ mEmulatorFriendlyName = "proximity";
+ mMsgFmt = mEmulatorFriendlyName + ":%g\0";
+ break;
+ case 11: // Sensor.TYPE_ROTATION_VECTOR is missing in API 7
+ mUiName = "Rotation";
+ // 3 floats.
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "rotation";
+ mMsgFmt = mEmulatorFriendlyName + ":%g:%g:%g\0";
+ break;
+ case Sensor.TYPE_TEMPERATURE:
+ mUiName = "Temperature";
+ // 1 integer.
+ mTextFmt = "%.0f";
+ mEmulatorFriendlyName = "tempterature";
+ mMsgFmt = mEmulatorFriendlyName + ":%g\0";
+ break;
+ default:
+ mUiName = "<Unknown>";
+ mTextFmt = "N/A";
+ mEmulatorFriendlyName = "unknown";
+ mMsgFmt = mEmulatorFriendlyName + "\0";
+ if (DEBUG) Log.e(TAG, "Unknown sensor type " + mSensor.getType() + " for sensor "
+ + mSensor.getName());
+ break;
+ }
+ }
+
+ public String getUiName() {
+ return mUiName;
+ }
+
+ public String getValue() {
+ return mValue;
+ }
+
+ public boolean isEnabledByEmulator() {
+ return mEnabledByEmulator;
+ }
+
+ public boolean isEnabledByUser() {
+ return mEnabledByUser;
+ }
+
+ /**
+ * Handles checked state change for the associated CheckBox. If check
+ * box is checked we will register sensor change listener. If it is
+ * unchecked, we will unregister sensor change listener.
+ */
+ public void onCheckedChanged(boolean isChecked) {
+ mEnabledByUser = isChecked;
+ if (isChecked) {
+ startListening();
+ } else {
+ stopListening();
+ }
+ }
+
+ // ---------
+
+ /**
+ * Gets sensor type.
+ *
+ * @return Sensor type as one of the Sensor.TYPE_XXX constants.
+ */
+ private int getType() {
+ return mSensor.getType();
+ }
+
+ /**
+ * Gets sensor's emulator-friendly name.
+ *
+ * @return Sensor's emulator-friendly name.
+ */
+ private String getEmulatorFriendlyName() {
+ return mEmulatorFriendlyName;
+ }
+
+ /**
+ * Starts monitoring the sensor. NOTE: This method is called from
+ * outside of the UI thread.
+ */
+ private void startListening() {
+ if (mEnabledByEmulator && mEnabledByUser) {
+ if (DEBUG) Log.d(TAG, "+++ Sensor " + getEmulatorFriendlyName() + " is started.");
+ mSenMan.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_UI);
+ }
+ }
+
+ /**
+ * Stops monitoring the sensor. NOTE: This method is called from outside
+ * of the UI thread.
+ */
+ private void stopListening() {
+ if (DEBUG) Log.d(TAG, "--- Sensor " + getEmulatorFriendlyName() + " is stopped.");
+ mSenMan.unregisterListener(mListener);
+ }
+
+ /**
+ * Enables sensor events. NOTE: This method is called from outside of
+ * the UI thread.
+ */
+ private void enableSensor() {
+ if (DEBUG) Log.d(TAG, ">>> Sensor " + getEmulatorFriendlyName() + " is enabled.");
+ mEnabledByEmulator = true;
+ mValue = "";
+ notifyUi(SENSOR_STATE_CHANGED, this, mEnabledByEmulator);
+ }
+
+ /**
+ * Disables sensor events. NOTE: This method is called from outside of
+ * the UI thread.
+ */
+ private void disableSensor() {
+ Log.e(TAG, "<<< Sensor " + getEmulatorFriendlyName() + " is disabled.");
+ mEnabledByEmulator = false;
+ mValue = "Disabled by emulator";
+ notifyUi(SENSOR_STATE_CHANGED, this, mEnabledByEmulator);
+ }
+
+ private class OurSensorEventListener implements SensorEventListener {
+ /**
+ * Handles "sensor changed" event. This is an implementation of the
+ * SensorEventListener interface.
+ */
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ // Display current sensor value, and format message that will be
+ // sent to the emulator.
+ final int nArgs = event.values.length;
+ String msg;
+ 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]);
+ } else if (nArgs == 2) {
+ val = String.format(mTextFmt, event.values[0], event.values[1]);
+ msg = 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]);
+ } else {
+ Log.e(TAG, "Unexpected number of values " + event.values.length
+ + " in onSensorChanged for sensor " + mSensor.getName());
+ return;
+ }
+ mValue = val;
+ sendSensorEvent(msg);
+ notifyUi(SENSOR_DISPLAY_MODIFIED, this, val);
+ }
+
+ /**
+ * Handles "sensor accuracy changed" event. This is an implementation of
+ * the SensorEventListener interface.
+ */
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+ }
+ } // MonitoredSensor
+
+}
diff --git a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/ControllerService.java b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/service/ControllerService.java
index b67ac45..3c2ab2f 100755
--- a/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/ControllerService.java
+++ b/apps/SdkController/SdkControllerApp/src/com/android/tools/sdkcontroller/service/ControllerService.java
@@ -14,10 +14,13 @@
* the License.
*/
-package com.android.tools.sdkcontroller;
+package com.android.tools.sdkcontroller.service;
+import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import android.app.Activity;
import android.app.Notification;
@@ -27,9 +30,18 @@ import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
-import android.os.SystemClock;
import android.util.Log;
+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.SensorsHandler;
+import com.android.tools.sdkcontroller.lib.EmulatorConnection;
+import com.android.tools.sdkcontroller.lib.EmulatorConnection.EmulatorConnectionType;
+import com.android.tools.sdkcontroller.lib.EmulatorListener;
+
/**
* The background service of the SdkController.
* There can be only one instance of this.
@@ -58,6 +70,8 @@ public class ControllerService extends Service {
/** Internal error reported by the service. */
private String mSensorError = "";
+ private final Set<EmuCnxHandler> mHandlers = new HashSet<ControllerService.EmuCnxHandler>();
+
/**
* Interface that the service uses to notify binded activities.
* <p/>
@@ -69,9 +83,13 @@ public class ControllerService extends Service {
/**
* The error string reported by the service has changed. <br/>
* Note this may be called from a thread different than the UI thread.
- * @param error The new error string.
*/
- void onErrorChanged(String error);
+ void onErrorChanged();
+
+ /**
+ * The service status has changed (emulator connected/disconnected.)
+ */
+ void onStatusChanged();
}
/** Interface that callers can use to access the service. */
@@ -83,6 +101,7 @@ public class ControllerService extends Service {
* @param listener A non-null listener. Ignored if already listed.
*/
public void addListener(ControllerListener listener) {
+ assert listener != null;
if (listener != null) {
synchronized(mListeners) {
if (!mListeners.contains(listener)) {
@@ -98,6 +117,7 @@ public class ControllerService extends Service {
* @param listener A listener to remove. Can be null.
*/
public void removeListener(ControllerListener listener) {
+ assert listener != null;
synchronized(mListeners) {
mListeners.remove(listener);
}
@@ -106,6 +126,25 @@ public class ControllerService extends Service {
public String getSensorErrors() {
return mSensorError;
}
+
+ public boolean isEmuConnected() {
+ for (EmuCnxHandler handler : mHandlers) {
+ if (!handler.isConnected()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public BaseHandler getHandler(HandlerType type) {
+ for (EmuCnxHandler handler : mHandlers) {
+ BaseHandler h = handler.getHandler();
+ if (h.getType() == type) {
+ return h;
+ }
+ }
+ return null;
+ }
}
/**
@@ -151,32 +190,131 @@ public class ControllerService extends Service {
// ------
/**
- * Called when the service has been created.
+ * Wrapper that associates one {@link EmulatorConnection} with
+ * one {@link BaseHandler}. Ideally we would not need this if all
+ * the action handlers were using the same port, so this wrapper
+ * is just temporary.
*/
- private void onServiceStarted() {
- // TODO: add stuff to do when the service starts (e.g. activate sensors?)
-
- // Hack: just do see if this is working, change the error field for a little while.
- Thread t = new Thread(new Runnable() {
- @Override
- public void run() {
- for (int i = 1; i <= 5 && gServiceIsRunning; i++) {
- SystemClock.sleep(1000); // 1s
- resetError();
- addError("Test msg from service thread " + i);
+ private class EmuCnxHandler implements EmulatorListener {
+
+ private EmulatorConnection mCnx;
+ private boolean mConnected;
+ private final BaseHandler mHandler;
+
+ public EmuCnxHandler(BaseHandler handler) {
+ mHandler = handler;
+ }
+
+ @Override
+ public void onEmulatorConnected() {
+ mConnected = true;
+ notifyStatusChanged();
+ }
+
+ @Override
+ public void onEmulatorDisconnected() {
+ mConnected = false;
+ notifyStatusChanged();
+ }
+
+ @Override
+ public String onEmulatorQuery(String query, String param) {
+ return mHandler.onEmulatorQuery(query, param);
+ }
+
+ @Override
+ public String onEmulatorBlobQuery(byte[] array) {
+ return mHandler.onEmulatorBlobQuery(array);
+ }
+
+ EmuCnxHandler connect() throws InterruptedException {
+ assert mCnx == null;
+
+ final EmuCnxHandler cnxHandler = this;
+
+ // Apps targeting Honeycomb SDK can't do network IO on their main UI
+ // thread. So just start the connection from a thread and then join it.
+ // Yes there's some irony in there.
+ Thread t = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mCnx = new EmulatorConnection(mHandler.getPort(),
+ EmulatorConnectionType.SYNC_CONNECTION,
+ cnxHandler);
+ } catch (IOException e) {
+ addError("Connection failed: " + e.toString());
+ Log.e(TAG, "EmuConnection failed");
+ }
}
- resetError();
+ }, "EmuCnxH.connect");
+ t.start();
+ t.join();
+
+ mHandler.onStart(mCnx, ControllerService.this /*context*/);
+ return this;
+ }
+
+ void disconnect() {
+ if (mCnx != null) {
+ mHandler.onStop();
+ mCnx.disconnect();
+ mCnx = null;
}
- });
- t.start();
+ }
+
+ boolean isConnected() {
+ return mConnected;
+ }
+
+ public BaseHandler getHandler() {
+ return mHandler;
+ }
+ }
+
+ private void disconnectAll() {
+ for(EmuCnxHandler handler : mHandlers) {
+ handler.disconnect();
+ }
+ mHandlers.clear();
+ }
+
+ /**
+ * Called when the service has been created.
+ */
+ private void onServiceStarted() {
+ try {
+ disconnectAll();
+
+ assert mHandlers.isEmpty();
+ mHandlers.add(new EmuCnxHandler(new MultitouchHandler()).connect());
+ mHandlers.add(new EmuCnxHandler(new SensorsHandler()).connect());
+ } catch (Exception e) {
+ addError("Connection failed: " + e.toString());
+ }
}
/**
* Called when the service is being destroyed.
*/
private void onServiceStopped() {
+ disconnectAll();
+ }
+
+ private void notifyErrorChanged() {
+ synchronized(mListeners) {
+ for (ControllerListener listener : mListeners) {
+ listener.onErrorChanged();
+ }
+ }
+ }
- // TODO: add stuff to do when the service stops (e.g. release sensors?)
+ private void notifyStatusChanged() {
+ synchronized(mListeners) {
+ for (ControllerListener listener : mListeners) {
+ listener.onStatusChanged();
+ }
+ }
}
/**
@@ -185,11 +323,7 @@ public class ControllerService extends Service {
private void resetError() {
mSensorError = "";
- synchronized(mListeners) {
- for (ControllerListener listener : mListeners) {
- listener.onErrorChanged(mSensorError);
- }
- }
+ notifyErrorChanged();
}
/**
@@ -203,11 +337,7 @@ public class ControllerService extends Service {
}
mSensorError += error;
- synchronized(mListeners) {
- for (ControllerListener listener : mListeners) {
- listener.onErrorChanged(mSensorError);
- }
- }
+ notifyErrorChanged();
}
/**
diff --git a/apps/SdkController/SdkControllerLib/src/com/android/tools/sdkcontroller/lib/EmulatorConnection.java b/apps/SdkController/SdkControllerLib/src/com/android/tools/sdkcontroller/lib/EmulatorConnection.java
index 8bf1fb2..aa21ca7 100755
--- a/apps/SdkController/SdkControllerLib/src/com/android/tools/sdkcontroller/lib/EmulatorConnection.java
+++ b/apps/SdkController/SdkControllerLib/src/com/android/tools/sdkcontroller/lib/EmulatorConnection.java
@@ -221,7 +221,7 @@ public class EmulatorConnection {
public void run() {
theReader();
}
- }).start();
+ }, "EmuSyncChannel").start();
}
/***********************************************************************
@@ -549,13 +549,16 @@ public class EmulatorConnection {
/**
* Constructs EmulatorConnection instance.
+ * <p/>
+ * Important: Apps targeting Honeycomb+ SDK are not allowed to do networking on their main
+ * thread. The caller is responsible to make sure this is NOT called from a main UI thread.
*
* @param port TCP port where emulator connects.
* @param ctype Defines connection type to use (sync / async). See comments
* to EmulatorConnection class for more info.
* @throws IOException
*/
- private void constructEmulator(int port, EmulatorConnectionType ctype) throws IOException {
+ private void constructEmulator(final int port, EmulatorConnectionType ctype) throws IOException {
mConnectionType = ctype;
// Create I/O looper.
mSelector = SelectorProvider.provider().openSelector();
@@ -565,12 +568,14 @@ public class EmulatorConnection {
mServerSocket = ServerSocketChannel.open();
mServerSocket.configureBlocking(false);
InetAddress local = InetAddress.getLocalHost();
- InetSocketAddress address = new InetSocketAddress(local, port);
+ final InetSocketAddress address = new InetSocketAddress(local, port);
mServerSocket.socket().bind(address);
// Register 'accept' I/O on the server socket.
mServerSocket.register(mSelector, SelectionKey.OP_ACCEPT);
+ // TODO we need a call back onBindSuccess(success or exception)
+
Logv("EmulatorConnection listener is created for port " + port);
// Start I/O looper and dispatcher.
new Thread(new Runnable() {
@@ -578,11 +583,14 @@ public class EmulatorConnection {
public void run() {
runIOLooper();
}
- }).start();
+ }, "EmuCnxIoLoop").start();
}
/**
* Sends a notification message to the emulator via 'event' channel.
+ * <p/>
+ * Important: Apps targeting Honeycomb+ SDK are not allowed to do networking on their main
+ * thread. The caller is responsible to make sure this is NOT called from a main UI thread.
*
* @param msg
*/