diff options
Diffstat (limited to 'apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java')
-rwxr-xr-x | apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java | 319 |
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); + } +} |