• 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.HashSet;
21 import java.util.List;
22 import java.util.Set;
23 
24 import android.app.Activity;
25 import android.app.Notification;
26 import android.app.NotificationManager;
27 import android.app.PendingIntent;
28 import android.app.Service;
29 import android.content.Intent;
30 import android.os.Binder;
31 import android.os.IBinder;
32 import android.util.Log;
33 
34 import com.android.tools.sdkcontroller.R;
35 import com.android.tools.sdkcontroller.activities.MainActivity;
36 import com.android.tools.sdkcontroller.handlers.BaseHandler;
37 import com.android.tools.sdkcontroller.handlers.BaseHandler.HandlerType;
38 import com.android.tools.sdkcontroller.handlers.MultiTouchHandler;
39 import com.android.tools.sdkcontroller.handlers.SensorsHandler;
40 import com.android.tools.sdkcontroller.lib.EmulatorConnection;
41 import com.android.tools.sdkcontroller.lib.EmulatorConnection.EmulatorConnectionType;
42 import com.android.tools.sdkcontroller.lib.EmulatorListener;
43 
44 /**
45  * The background service of the SdkController.
46  * There can be only one instance of this.
47  * <p/>
48  * The service manages a number of action "handlers" which can be seen as individual tasks
49  * that the user might want to accomplish, for example "sending sensor data to the emulator"
50  * or "sending multi-touch data and displaying an emulator screen".
51  * <p/>
52  * Each handler currently has its own emulator connection associated to it (cf class
53  * {@code EmuCnxHandler} below. However our goal is to later move to a single connection channel
54  * with all data multiplexed on top of it.
55  * <p/>
56  * All the handlers are created when the service starts, and whether the emulator connection
57  * is successful or not, and whether there's any UI to control it. It's up to the handlers
58  * to deal with these specific details. <br/>
59  * For example the {@link SensorsHandler} initializes its sensor list as soon as created
60  * and then tries to send data as soon as there's an emulator connection.
61  * On the other hand the {@link MultiTouchHandler} lays dormant till there's an UI interacting
62  * with it.
63  */
64 public class ControllerService extends Service {
65 
66     /*
67      * Implementation reference:
68      * http://developer.android.com/reference/android/app/Service.html#LocalServiceSample
69      */
70 
71     public static String TAG = ControllerService.class.getSimpleName();
72     private static boolean DEBUG = true;
73 
74     /** Identifier for the notification. */
75     private static int NOTIF_ID = 'S' << 24 + 'd' << 16 + 'k' << 8 + 'C' << 0;
76 
77     private final IBinder mBinder = new ControllerBinder();
78 
79     private List<ControllerListener> mListeners = new ArrayList<ControllerListener>();
80 
81     /**
82      * Whether the service is running. Set to true in onCreate, false in onDestroy.
83      */
84     private static volatile boolean gServiceIsRunning = false;
85 
86     /** Internal error reported by the service. */
87     private String mServiceError = "";
88 
89     private final Set<EmuCnxHandler> mHandlers = new HashSet<ControllerService.EmuCnxHandler>();
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>all</all> the communication channels for all handlers
153          * are properly connected.
154          *
155          * @return True if all the handler's communication channels are connected.
156          */
isEmuConnected()157         public boolean isEmuConnected() {
158             for (EmuCnxHandler handler : mHandlers) {
159                 if (!handler.isConnected()) {
160                     return false;
161                 }
162             }
163             return true;
164         }
165 
166         /**
167          * Returns the handler for the given type.
168          *
169          * @param type One of the {@link HandlerType}s. Must not be null.
170          * @return Null if the type is not found, otherwise the handler's unique instance.
171          */
getHandler(HandlerType type)172         public BaseHandler getHandler(HandlerType type) {
173             for (EmuCnxHandler handler : mHandlers) {
174                 BaseHandler h = handler.getHandler();
175                 if (h.getType() == type) {
176                     return h;
177                 }
178             }
179             return null;
180         }
181     }
182 
183     /**
184      * Whether the service is running. Set to true in onCreate, false in onDestroy.
185      */
isServiceIsRunning()186     public static boolean isServiceIsRunning() {
187         return gServiceIsRunning;
188     }
189 
190     @Override
onCreate()191     public void onCreate() {
192         super.onCreate();
193         if (DEBUG) Log.d(TAG, "Service onCreate");
194         gServiceIsRunning = true;
195         showNotification();
196         onServiceStarted();
197     }
198 
199     @Override
onStartCommand(Intent intent, int flags, int startId)200     public int onStartCommand(Intent intent, int flags, int startId) {
201         // We want this service to continue running until it is explicitly
202         // stopped, so return sticky.
203         if (DEBUG) Log.d(TAG, "Service onStartCommand");
204         return START_STICKY;
205     }
206 
207     @Override
onBind(Intent intent)208     public IBinder onBind(Intent intent) {
209         if (DEBUG) Log.d(TAG, "Service onBind");
210         return mBinder;
211     }
212 
213     @Override
onDestroy()214     public void onDestroy() {
215         if (DEBUG) Log.d(TAG, "Service onDestroy");
216         gServiceIsRunning = false;
217         removeNotification();
218         resetError();
219         onServiceStopped();
220         super.onDestroy();
221     }
222 
223     // ------
224 
225     /**
226      * Wrapper that associates one {@link EmulatorConnection} with
227      * one {@link BaseHandler}. Ideally we would not need this if all
228      * the action handlers were using the same port, so this wrapper
229      * is just temporary.
230      */
231     private class EmuCnxHandler implements EmulatorListener {
232 
233         private EmulatorConnection mCnx;
234         private boolean mConnected;
235         private final BaseHandler mHandler;
236 
EmuCnxHandler(BaseHandler handler)237         public EmuCnxHandler(BaseHandler handler) {
238             mHandler = handler;
239         }
240 
241         @Override
onEmulatorConnected()242         public void onEmulatorConnected() {
243             mConnected = true;
244             notifyStatusChanged();
245         }
246 
247         @Override
onEmulatorDisconnected()248         public void onEmulatorDisconnected() {
249             mConnected = false;
250             notifyStatusChanged();
251         }
252 
253         @Override
onEmulatorQuery(String query, String param)254         public String onEmulatorQuery(String query, String param) {
255             if (DEBUG) Log.d(TAG, mHandler.getType().toString() +  " Query " + query);
256             return mHandler.onEmulatorQuery(query, param);
257         }
258 
259         @Override
onEmulatorBlobQuery(byte[] array)260         public String onEmulatorBlobQuery(byte[] array) {
261             if (DEBUG) Log.d(TAG, mHandler.getType().toString() +  " BlobQuery " + array.length);
262             return mHandler.onEmulatorBlobQuery(array);
263         }
264 
connect()265         EmuCnxHandler connect() {
266             assert mCnx == null;
267 
268             mCnx = new EmulatorConnection(this);
269 
270             // Apps targeting Honeycomb SDK can't do network IO on their main UI
271             // thread. So just start the connection from a thread.
272             Thread t = new Thread(new Runnable() {
273                 @Override
274                 public void run() {
275                     // This will call onEmulatorBindResult with the result.
276                     mCnx.connect(mHandler.getPort(), EmulatorConnectionType.SYNC_CONNECTION);
277                 }
278             }, "EmuCnxH.connect-" + mHandler.getType().toString());
279             t.start();
280 
281             return this;
282         }
283 
284         @Override
onEmulatorBindResult(boolean success, Exception e)285         public void onEmulatorBindResult(boolean success, Exception e) {
286             if (success) {
287                 mHandler.onStart(mCnx, ControllerService.this /*context*/);
288             } else {
289                 Log.e(TAG, "EmuCnx failed for " + mHandler.getType(), e);
290                 String msg = mHandler.getType().toString() + " failed: " +
291                     (e == null ? "n/a" : e.toString());
292                 addError(msg);
293             }
294         }
295 
disconnect()296         void disconnect() {
297             if (mCnx != null) {
298                 mHandler.onStop();
299                 mCnx.disconnect();
300                 mCnx = null;
301             }
302         }
303 
isConnected()304         boolean isConnected() {
305             return mConnected;
306         }
307 
getHandler()308         public BaseHandler getHandler() {
309             return mHandler;
310         }
311     }
312 
disconnectAll()313     private void disconnectAll() {
314         for(EmuCnxHandler handler : mHandlers) {
315             handler.disconnect();
316         }
317         mHandlers.clear();
318     }
319 
320     /**
321      * Called when the service has been created.
322      */
onServiceStarted()323     private void onServiceStarted() {
324         try {
325             disconnectAll();
326 
327             assert mHandlers.isEmpty();
328             mHandlers.add(new EmuCnxHandler(new MultiTouchHandler()).connect());
329             mHandlers.add(new EmuCnxHandler(new SensorsHandler()).connect());
330         } catch (Exception e) {
331             addError("Connection failed: " + e.toString());
332         }
333     }
334 
335     /**
336      * Called when the service is being destroyed.
337      */
onServiceStopped()338     private void onServiceStopped() {
339         disconnectAll();
340     }
341 
notifyErrorChanged()342     private void notifyErrorChanged() {
343         synchronized(mListeners) {
344             for (ControllerListener listener : mListeners) {
345                 listener.onErrorChanged();
346             }
347         }
348     }
349 
notifyStatusChanged()350     private void notifyStatusChanged() {
351         synchronized(mListeners) {
352             for (ControllerListener listener : mListeners) {
353                 listener.onStatusChanged();
354             }
355         }
356     }
357 
358     /**
359      * Resets the error string and notify listeners.
360      */
resetError()361     private void resetError() {
362         mServiceError = "";
363 
364         notifyErrorChanged();
365     }
366 
367     /**
368      * An internal utility method to add a line to the error string and notify listeners.
369      * @param error A non-null non-empty error line. \n will be added automatically.
370      */
addError(String error)371     private void addError(String error) {
372         Log.e(TAG, error);
373         if (mServiceError.length() > 0) {
374             mServiceError += "\n";
375         }
376         mServiceError += error;
377 
378         notifyErrorChanged();
379     }
380 
381     /**
382      * Displays a notification showing that the service is running.
383      * When the user touches the notification, it opens the main activity
384      * which allows the user to stop this service.
385      */
386     @SuppressWarnings("deprecated")
showNotification()387     private void showNotification() {
388         NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
389 
390         String text = getString(R.string.service_notif_title);
391 
392         // Note: Notification is marked as deprecated -- in API 11+ there's a new Builder class
393         // but we need to have API 7 compatibility so we ignore that warning.
394 
395         Notification n = new Notification(R.drawable.ic_launcher, text, System.currentTimeMillis());
396         n.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR;
397         Intent intent = new Intent(this, MainActivity.class);
398         intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
399         PendingIntent pi = PendingIntent.getActivity(
400                 this,     //context
401                 0,        //requestCode
402                 intent,   //intent
403                 0         // pending intent flags
404                 );
405         n.setLatestEventInfo(this, text, text, pi);
406 
407         nm.notify(NOTIF_ID, n);
408     }
409 
removeNotification()410     private void removeNotification() {
411         NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
412         nm.cancel(NOTIF_ID);
413     }
414 }
415