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