• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          */
onErrorChanged()103         void onErrorChanged();
104 
105         /**
106          * The service status has changed (emulator connected/disconnected.)
107          */
onStatusChanged()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          */
addControllerListener(ControllerListener listener)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          */
removeControllerListener(ControllerListener listener)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          */
getServiceError()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          */
isEmuConnected()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          */
getChannel(String name)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      */
isServiceIsRunning()176     public static boolean isServiceIsRunning() {
177         return gServiceIsRunning;
178     }
179 
180     @Override
onCreate()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
onStartCommand(Intent intent, int flags, int startId)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
onBind(Intent intent)198     public IBinder onBind(Intent intent) {
199         if (DEBUG) Log.d(TAG, "Service onBind");
200         return mBinder;
201     }
202 
203     @Override
onDestroy()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 
disconnectAll()213     private void disconnectAll() {
214         if (mConnection != null) {
215             mConnection.disconnect();
216         }
217     }
218 
219     /**
220      * Called when the service has been created.
221      */
onServiceStarted()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      */
onServiceStopped()243     private void onServiceStopped() {
244         disconnectAll();
245     }
246 
notifyErrorChanged()247     private void notifyErrorChanged() {
248         synchronized (mListeners) {
249             for (ControllerListener listener : mListeners) {
250                 listener.onErrorChanged();
251             }
252         }
253     }
254 
notifyStatusChanged()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      */
resetError()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      */
addError(String error)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")
showNotification()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 
removeNotification()315     private void removeNotification() {
316         NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
317         nm.cancel(NOTIF_ID);
318     }
319 }
320