Home | History | Annotate | Download | only in service
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.android.tools.sdkcontroller.service;
     18 
     19 import java.util.ArrayList;
     20 import java.util.List;
     21 
     22 import android.app.Activity;
     23 import android.app.Notification;
     24 import android.app.NotificationManager;
     25 import android.app.PendingIntent;
     26 import android.app.Service;
     27 import android.content.Intent;
     28 import android.os.Binder;
     29 import android.os.IBinder;
     30 import android.util.Log;
     31 
     32 import com.android.tools.sdkcontroller.R;
     33 import com.android.tools.sdkcontroller.activities.MainActivity;
     34 import com.android.tools.sdkcontroller.handlers.MultiTouchChannel;
     35 import com.android.tools.sdkcontroller.handlers.SensorChannel;
     36 import com.android.tools.sdkcontroller.lib.Connection;
     37 import com.android.tools.sdkcontroller.lib.Channel;
     38 
     39 /**
     40  * The background service of the SdkController.
     41  * There can be only one instance of this.
     42  * <p/>
     43  * The service manages a number of SDK controller channels which can be seen as
     44  * individual tasks that the user might want to accomplish, for example "sending
     45  * sensor data to the emulator" or "sending multi-touch data and displaying an
     46  * emulator screen".
     47  * <p/>
     48  * Each channel connects to the emulator via UNIX-domain socket that is bound to
     49  * "android.sdk.controller" port. Connection class provides a socket server that
     50  * listens to emulator connections on this port, and binds new connection with a
     51  * channel, based on channel name.
     52  * <p/>
     53  * All the channels are created when the service starts, and whether the emulator
     54  * connection is successful or not, and whether there's any UI to control it.
     55  * It's up to the channels to deal with these specific details. <br/>
     56  * For example the {@link SensorChannel} initializes its sensor list as soon as
     57  * created and then tries to send data as soon as there's an emulator
     58  * connection. On the other hand the {@link MultiTouchChannel} lays dormant till
     59  * there's an UI interacting with it.
     60  */
     61 public class ControllerService extends Service {
     62 
     63     /*
     64      * Implementation reference:
     65      * http://developer.android.com/reference/android/app/Service.html#LocalServiceSample
     66      */
     67 
     68     /** Tag for logging messages. */
     69     public static String TAG = ControllerService.class.getSimpleName();
     70     /** Controls debug log. */
     71     private static boolean DEBUG = true;
     72     /** Identifier for the notification. */
     73     private static int NOTIF_ID = 'S' << 24 + 'd' << 16 + 'k' << 8 + 'C' << 0;
     74 
     75     /** Connection to the emulator. */
     76     public Connection mConnection;
     77 
     78 
     79     private final IBinder mBinder = new ControllerBinder();
     80 
     81     private List<ControllerListener> mListeners = new ArrayList<ControllerListener>();
     82 
     83     /**
     84      * Whether the service is running. Set to true in onCreate, false in onDestroy.
     85      */
     86     private static volatile boolean gServiceIsRunning = false;
     87 
     88     /** Internal error reported by the service. */
     89     private String mServiceError = "";
     90 
     91     /**
     92      * Interface that the service uses to notify binded activities.
     93      * <p/>
     94      * As a design rule, implementations of this listener should be aware that most calls
     95      * will NOT happen on the UI thread. Any access to the UI should be properly protected
     96      * by using {@link Activity#runOnUiThread(Runnable)}.
     97      */
     98     public interface ControllerListener {
     99         /**
    100          * The error string reported by the service has changed. <br/>
    101          * Note this may be called from a thread different than the UI thread.
    102          */
    103         void onErrorChanged();
    104 
    105         /**
    106          * The service status has changed (emulator connected/disconnected.)
    107          */
    108         void onStatusChanged();
    109     }
    110 
    111     /** Interface that callers can use to access the service. */
    112     public class ControllerBinder extends Binder {
    113 
    114         /**
    115          * Adds a new listener that will be notified when the service state changes.
    116          *
    117          * @param listener A non-null listener. Ignored if already listed.
    118          */
    119         public void addControllerListener(ControllerListener listener) {
    120             assert listener != null;
    121             if (listener != null) {
    122                 synchronized (mListeners) {
    123                     if (!mListeners.contains(listener)) {
    124                         mListeners.add(listener);
    125                     }
    126                 }
    127             }
    128         }
    129 
    130         /**
    131          * Removes a listener.
    132          *
    133          * @param listener A listener to remove. Can be null.
    134          */
    135         public void removeControllerListener(ControllerListener listener) {
    136             assert listener != null;
    137             synchronized (mListeners) {
    138                 mListeners.remove(listener);
    139             }
    140         }
    141 
    142         /**
    143          * Returns the error string accumulated by the service.
    144          * Typically these would relate to failures to establish the communication
    145          * channel(s) with the emulator, or unexpected disconnections.
    146          */
    147         public String getServiceError() {
    148             return mServiceError;
    149         }
    150 
    151         /**
    152          * Indicates when <em>any</all> of the SDK controller channels is connected
    153          * with the emulator.
    154          *
    155          * @return True if any of the SDK controller channels is connected with the
    156          *         emulator.
    157          */
    158         public boolean isEmuConnected() {
    159             return mConnection.isEmulatorConnected();
    160         }
    161 
    162         /**
    163          * Returns the channel instance for the given type.
    164          *
    165          * @param name One of the channel names. Must not be null.
    166          * @return Null if the type is not found, otherwise the handler's unique instance.
    167          */
    168         public Channel getChannel(String name) {
    169             return mConnection.getChannel(name);
    170         }
    171     }
    172 
    173     /**
    174      * Whether the service is running. Set to true in onCreate, false in onDestroy.
    175      */
    176     public static boolean isServiceIsRunning() {
    177         return gServiceIsRunning;
    178     }
    179 
    180     @Override
    181     public void onCreate() {
    182         super.onCreate();
    183         if (DEBUG) Log.d(TAG, "Service onCreate");
    184         gServiceIsRunning = true;
    185         showNotification();
    186         onServiceStarted();
    187     }
    188 
    189     @Override
    190     public int onStartCommand(Intent intent, int flags, int startId) {
    191         // We want this service to continue running until it is explicitly
    192         // stopped, so return sticky.
    193         if (DEBUG) Log.d(TAG, "Service onStartCommand");
    194         return START_STICKY;
    195     }
    196 
    197     @Override
    198     public IBinder onBind(Intent intent) {
    199         if (DEBUG) Log.d(TAG, "Service onBind");
    200         return mBinder;
    201     }
    202 
    203     @Override
    204     public void onDestroy() {
    205         if (DEBUG) Log.d(TAG, "Service onDestroy");
    206         gServiceIsRunning = false;
    207         removeNotification();
    208         resetError();
    209         onServiceStopped();
    210         super.onDestroy();
    211     }
    212 
    213     private void disconnectAll() {
    214         if (mConnection != null) {
    215             mConnection.disconnect();
    216         }
    217     }
    218 
    219     /**
    220      * Called when the service has been created.
    221      */
    222     private void onServiceStarted() {
    223         try {
    224             disconnectAll();
    225 
    226             // Bind to SDK controller port, and start accepting emulator
    227             // connections.
    228             mConnection = new Connection(ControllerService.this);
    229             mConnection.connect();
    230 
    231             // Create and register sensors channel.
    232             mConnection.registerChannel(new SensorChannel(ControllerService.this));
    233             // Create and register multi-touch channel.
    234             mConnection.registerChannel(new MultiTouchChannel(ControllerService.this));
    235         } catch (Exception e) {
    236             addError("Connection failed: " + e.toString());
    237         }
    238     }
    239 
    240     /**
    241      * Called when the service is being destroyed.
    242      */
    243     private void onServiceStopped() {
    244         disconnectAll();
    245     }
    246 
    247     private void notifyErrorChanged() {
    248         synchronized (mListeners) {
    249             for (ControllerListener listener : mListeners) {
    250                 listener.onErrorChanged();
    251             }
    252         }
    253     }
    254 
    255     public void notifyStatusChanged() {
    256         synchronized (mListeners) {
    257             for (ControllerListener listener : mListeners) {
    258                 listener.onStatusChanged();
    259             }
    260         }
    261     }
    262 
    263     /**
    264      * Resets the error string and notify listeners.
    265      */
    266     private void resetError() {
    267         mServiceError = "";
    268 
    269         notifyErrorChanged();
    270     }
    271 
    272     /**
    273      * An internal utility method to add a line to the error string and notify listeners.
    274      * @param error A non-null non-empty error line. \n will be added automatically.
    275      */
    276     public void addError(String error) {
    277         Log.e(TAG, error);
    278         if (mServiceError.length() > 0) {
    279             mServiceError += "\n";
    280         }
    281         mServiceError += error;
    282 
    283         notifyErrorChanged();
    284     }
    285 
    286     /**
    287      * Displays a notification showing that the service is running.
    288      * When the user touches the notification, it opens the main activity
    289      * which allows the user to stop this service.
    290      */
    291     @SuppressWarnings("deprecated")
    292     private void showNotification() {
    293         NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    294 
    295         String text = getString(R.string.service_notif_title);
    296 
    297         // Note: Notification is marked as deprecated -- in API 11+ there's a new Builder class
    298         // but we need to have API 7 compatibility so we ignore that warning.
    299 
    300         Notification n = new Notification(R.drawable.ic_launcher, text, System.currentTimeMillis());
    301         n.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR;
    302         Intent intent = new Intent(this, MainActivity.class);
    303         intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
    304         PendingIntent pi = PendingIntent.getActivity(
    305                 this,   //context
    306                 0,      //requestCode
    307                 intent, //intent
    308                 0       //pending intent flags
    309                 );
    310         n.setLatestEventInfo(this, text, text, pi);
    311 
    312         nm.notify(NOTIF_ID, n);
    313     }
    314 
    315     private void removeNotification() {
    316         NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    317         nm.cancel(NOTIF_ID);
    318     }
    319 }
    320