aboutsummaryrefslogtreecommitdiff
path: root/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java
diff options
context:
space:
mode:
Diffstat (limited to 'apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java')
-rwxr-xr-xapps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java319
1 files changed, 319 insertions, 0 deletions
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java b/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java
new file mode 100755
index 000000000..9a3408b3e
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java
@@ -0,0 +1,319 @@
+/*
+ * 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.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.tools.sdkcontroller.R;
+import com.android.tools.sdkcontroller.activities.MainActivity;
+import com.android.tools.sdkcontroller.handlers.MultiTouchChannel;
+import com.android.tools.sdkcontroller.handlers.SensorChannel;
+import com.android.tools.sdkcontroller.lib.Connection;
+import com.android.tools.sdkcontroller.lib.Channel;
+
+/**
+ * The background service of the SdkController.
+ * There can be only one instance of this.
+ * <p/>
+ * The service manages a number of SDK controller channels which can be seen as
+ * individual tasks that the user might want to accomplish, for example "sending
+ * sensor data to the emulator" or "sending multi-touch data and displaying an
+ * emulator screen".
+ * <p/>
+ * Each channel connects to the emulator via UNIX-domain socket that is bound to
+ * "android.sdk.controller" port. Connection class provides a socket server that
+ * listens to emulator connections on this port, and binds new connection with a
+ * channel, based on channel name.
+ * <p/>
+ * All the channels are created when the service starts, and whether the emulator
+ * connection is successful or not, and whether there's any UI to control it.
+ * It's up to the channels to deal with these specific details. <br/>
+ * For example the {@link SensorChannel} 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 MultiTouchChannel} lays dormant till
+ * there's an UI interacting with it.
+ */
+public class ControllerService extends Service {
+
+ /*
+ * Implementation reference:
+ * http://developer.android.com/reference/android/app/Service.html#LocalServiceSample
+ */
+
+ /** Tag for logging messages. */
+ public static String TAG = ControllerService.class.getSimpleName();
+ /** Controls debug log. */
+ private static boolean DEBUG = true;
+ /** Identifier for the notification. */
+ private static int NOTIF_ID = 'S' << 24 + 'd' << 16 + 'k' << 8 + 'C' << 0;
+
+ /** Connection to the emulator. */
+ public Connection mConnection;
+
+
+ private final IBinder mBinder = new ControllerBinder();
+
+ private List<ControllerListener> mListeners = new ArrayList<ControllerListener>();
+
+ /**
+ * Whether the service is running. Set to true in onCreate, false in onDestroy.
+ */
+ private static volatile boolean gServiceIsRunning = false;
+
+ /** Internal error reported by the service. */
+ private String mServiceError = "";
+
+ /**
+ * Interface that the service uses to notify binded activities.
+ * <p/>
+ * As a design rule, implementations of this listener should be aware that most calls
+ * will NOT happen on the UI thread. Any access to the UI should be properly protected
+ * by using {@link Activity#runOnUiThread(Runnable)}.
+ */
+ public interface ControllerListener {
+ /**
+ * The error string reported by the service has changed. <br/>
+ * Note this may be called from a thread different than the UI thread.
+ */
+ void onErrorChanged();
+
+ /**
+ * The service status has changed (emulator connected/disconnected.)
+ */
+ void onStatusChanged();
+ }
+
+ /** Interface that callers can use to access the service. */
+ public class ControllerBinder extends Binder {
+
+ /**
+ * Adds a new listener that will be notified when the service state changes.
+ *
+ * @param listener A non-null listener. Ignored if already listed.
+ */
+ public void addControllerListener(ControllerListener listener) {
+ assert listener != null;
+ if (listener != null) {
+ synchronized (mListeners) {
+ if (!mListeners.contains(listener)) {
+ mListeners.add(listener);
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes a listener.
+ *
+ * @param listener A listener to remove. Can be null.
+ */
+ public void removeControllerListener(ControllerListener listener) {
+ assert listener != null;
+ synchronized (mListeners) {
+ mListeners.remove(listener);
+ }
+ }
+
+ /**
+ * 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>any</all> of the SDK controller channels is connected
+ * with the emulator.
+ *
+ * @return True if any of the SDK controller channels is connected with the
+ * emulator.
+ */
+ public boolean isEmuConnected() {
+ return mConnection.isEmulatorConnected();
+ }
+
+ /**
+ * Returns the channel instance for the given type.
+ *
+ * @param name One of the channel names. Must not be null.
+ * @return Null if the type is not found, otherwise the handler's unique instance.
+ */
+ public Channel getChannel(String name) {
+ return mConnection.getChannel(name);
+ }
+ }
+
+ /**
+ * Whether the service is running. Set to true in onCreate, false in onDestroy.
+ */
+ public static boolean isServiceIsRunning() {
+ return gServiceIsRunning;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (DEBUG) Log.d(TAG, "Service onCreate");
+ gServiceIsRunning = true;
+ showNotification();
+ onServiceStarted();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ // We want this service to continue running until it is explicitly
+ // stopped, so return sticky.
+ if (DEBUG) Log.d(TAG, "Service onStartCommand");
+ return START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (DEBUG) Log.d(TAG, "Service onBind");
+ return mBinder;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (DEBUG) Log.d(TAG, "Service onDestroy");
+ gServiceIsRunning = false;
+ removeNotification();
+ resetError();
+ onServiceStopped();
+ super.onDestroy();
+ }
+
+ private void disconnectAll() {
+ if (mConnection != null) {
+ mConnection.disconnect();
+ }
+ }
+
+ /**
+ * Called when the service has been created.
+ */
+ private void onServiceStarted() {
+ try {
+ disconnectAll();
+
+ // Bind to SDK controller port, and start accepting emulator
+ // connections.
+ mConnection = new Connection(ControllerService.this);
+ mConnection.connect();
+
+ // Create and register sensors channel.
+ mConnection.registerChannel(new SensorChannel(ControllerService.this));
+ // Create and register multi-touch channel.
+ mConnection.registerChannel(new MultiTouchChannel(ControllerService.this));
+ } 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();
+ }
+ }
+ }
+
+ public void notifyStatusChanged() {
+ synchronized (mListeners) {
+ for (ControllerListener listener : mListeners) {
+ listener.onStatusChanged();
+ }
+ }
+ }
+
+ /**
+ * Resets the error string and notify listeners.
+ */
+ private void resetError() {
+ mServiceError = "";
+
+ notifyErrorChanged();
+ }
+
+ /**
+ * An internal utility method to add a line to the error string and notify listeners.
+ * @param error A non-null non-empty error line. \n will be added automatically.
+ */
+ public void addError(String error) {
+ Log.e(TAG, error);
+ if (mServiceError.length() > 0) {
+ mServiceError += "\n";
+ }
+ mServiceError += error;
+
+ notifyErrorChanged();
+ }
+
+ /**
+ * Displays a notification showing that the service is running.
+ * When the user touches the notification, it opens the main activity
+ * which allows the user to stop this service.
+ */
+ @SuppressWarnings("deprecated")
+ private void showNotification() {
+ NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+
+ String text = getString(R.string.service_notif_title);
+
+ // Note: Notification is marked as deprecated -- in API 11+ there's a new Builder class
+ // but we need to have API 7 compatibility so we ignore that warning.
+
+ Notification n = new Notification(R.drawable.ic_launcher, text, System.currentTimeMillis());
+ n.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR;
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ PendingIntent pi = PendingIntent.getActivity(
+ this, //context
+ 0, //requestCode
+ intent, //intent
+ 0 //pending intent flags
+ );
+ n.setLatestEventInfo(this, text, text, pi);
+
+ nm.notify(NOTIF_ID, n);
+ }
+
+ private void removeNotification() {
+ NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ nm.cancel(NOTIF_ID);
+ }
+}