1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.apis.app; 18 19 import android.app.Activity; 20 import android.app.Notification; 21 import android.app.NotificationManager; 22 import android.app.PendingIntent; 23 import android.app.Service; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.ServiceConnection; 28 import android.os.Bundle; 29 import android.os.RemoteException; 30 import android.os.Handler; 31 import android.os.IBinder; 32 import android.os.Message; 33 import android.os.Process; 34 import android.os.RemoteCallbackList; 35 import android.view.View; 36 import android.view.View.OnClickListener; 37 import android.widget.Button; 38 import android.widget.TextView; 39 import android.widget.Toast; 40 41 // Need the following import to get access to the app resources, since this 42 // class is in a sub-package. 43 import com.example.android.apis.R; 44 45 /** 46 * This is an example of implementing an application service that runs in a 47 * different process than the application. Because it can be in another 48 * process, we must use IPC to interact with it. The 49 * {@link Controller} and {@link Binding} classes 50 * show how to interact with the service. 51 * 52 * <p>Note that most applications <strong>do not</strong> need to deal with 53 * the complexity shown here. If your application simply has a service 54 * running in its own process, the {@link LocalService} sample shows a much 55 * simpler way to interact with it. 56 */ 57 public class RemoteService extends Service { 58 /** 59 * This is a list of callbacks that have been registered with the 60 * service. Note that this is package scoped (instead of private) so 61 * that it can be accessed more efficiently from inner classes. 62 */ 63 final RemoteCallbackList<IRemoteServiceCallback> mCallbacks 64 = new RemoteCallbackList<IRemoteServiceCallback>(); 65 66 int mValue = 0; 67 NotificationManager mNM; 68 69 @Override 70 public void onCreate() { 71 mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); 72 73 // Display a notification about us starting. 74 showNotification(); 75 76 // While this service is running, it will continually increment a 77 // number. Send the first message that is used to perform the 78 // increment. 79 mHandler.sendEmptyMessage(REPORT_MSG); 80 } 81 82 @Override 83 public void onDestroy() { 84 // Cancel the persistent notification. 85 mNM.cancel(R.string.remote_service_started); 86 87 // Tell the user we stopped. 88 Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show(); 89 90 // Unregister all callbacks. 91 mCallbacks.kill(); 92 93 // Remove the next pending message to increment the counter, stopping 94 // the increment loop. 95 mHandler.removeMessages(REPORT_MSG); 96 } 97 98 // BEGIN_INCLUDE(exposing_a_service) 99 @Override 100 public IBinder onBind(Intent intent) { 101 // Select the interface to return. If your service only implements 102 // a single interface, you can just return it here without checking 103 // the Intent. 104 if (IRemoteService.class.getName().equals(intent.getAction())) { 105 return mBinder; 106 } 107 if (ISecondary.class.getName().equals(intent.getAction())) { 108 return mSecondaryBinder; 109 } 110 return null; 111 } 112 113 /** 114 * The IRemoteInterface is defined through IDL 115 */ 116 private final IRemoteService.Stub mBinder = new IRemoteService.Stub() { 117 public void registerCallback(IRemoteServiceCallback cb) { 118 if (cb != null) mCallbacks.register(cb); 119 } 120 public void unregisterCallback(IRemoteServiceCallback cb) { 121 if (cb != null) mCallbacks.unregister(cb); 122 } 123 }; 124 125 /** 126 * A secondary interface to the service. 127 */ 128 private final ISecondary.Stub mSecondaryBinder = new ISecondary.Stub() { 129 public int getPid() { 130 return Process.myPid(); 131 } 132 public void basicTypes(int anInt, long aLong, boolean aBoolean, 133 float aFloat, double aDouble, String aString) { 134 } 135 }; 136 // END_INCLUDE(exposing_a_service) 137 138 private static final int REPORT_MSG = 1; 139 140 /** 141 * Our Handler used to execute operations on the main thread. This is used 142 * to schedule increments of our value. 143 */ 144 private final Handler mHandler = new Handler() { 145 @Override public void handleMessage(Message msg) { 146 switch (msg.what) { 147 148 // It is time to bump the value! 149 case REPORT_MSG: { 150 // Up it goes. 151 int value = ++mValue; 152 153 // Broadcast to all clients the new value. 154 final int N = mCallbacks.beginBroadcast(); 155 for (int i=0; i<N; i++) { 156 try { 157 mCallbacks.getBroadcastItem(i).valueChanged(value); 158 } catch (RemoteException e) { 159 // The RemoteCallbackList will take care of removing 160 // the dead object for us. 161 } 162 } 163 mCallbacks.finishBroadcast(); 164 165 // Repeat every 1 second. 166 sendMessageDelayed(obtainMessage(REPORT_MSG), 1*1000); 167 } break; 168 default: 169 super.handleMessage(msg); 170 } 171 } 172 }; 173 174 /** 175 * Show a notification while this service is running. 176 */ 177 private void showNotification() { 178 // In this sample, we'll use the same text for the ticker and the expanded notification 179 CharSequence text = getText(R.string.remote_service_started); 180 181 // Set the icon, scrolling text and timestamp 182 Notification notification = new Notification(R.drawable.stat_sample, text, 183 System.currentTimeMillis()); 184 185 // The PendingIntent to launch our activity if the user selects this notification 186 PendingIntent contentIntent = PendingIntent.getActivity(this, 0, 187 new Intent(this, Controller.class), 0); 188 189 // Set the info for the views that show in the notification panel. 190 notification.setLatestEventInfo(this, getText(R.string.remote_service_label), 191 text, contentIntent); 192 193 // Send the notification. 194 // We use a string id because it is a unique number. We use it later to cancel. 195 mNM.notify(R.string.remote_service_started, notification); 196 } 197 198 // ---------------------------------------------------------------------- 199 200 /** 201 * <p>Example of explicitly starting and stopping the remove service. 202 * This demonstrates the implementation of a service that runs in a different 203 * process than the rest of the application, which is explicitly started and stopped 204 * as desired.</p> 205 * 206 * <p>Note that this is implemented as an inner class only keep the sample 207 * all together; typically this code would appear in some separate class. 208 */ 209 public static class Controller extends Activity { 210 @Override 211 protected void onCreate(Bundle savedInstanceState) { 212 super.onCreate(savedInstanceState); 213 214 setContentView(R.layout.remote_service_controller); 215 216 // Watch for button clicks. 217 Button button = (Button)findViewById(R.id.start); 218 button.setOnClickListener(mStartListener); 219 button = (Button)findViewById(R.id.stop); 220 button.setOnClickListener(mStopListener); 221 } 222 223 private OnClickListener mStartListener = new OnClickListener() { 224 public void onClick(View v) { 225 // Make sure the service is started. It will continue running 226 // until someone calls stopService(). 227 // We use an action code here, instead of explictly supplying 228 // the component name, so that other packages can replace 229 // the service. 230 startService(new Intent( 231 "com.example.android.apis.app.REMOTE_SERVICE")); 232 } 233 }; 234 235 private OnClickListener mStopListener = new OnClickListener() { 236 public void onClick(View v) { 237 // Cancel a previous call to startService(). Note that the 238 // service will not actually stop at this point if there are 239 // still bound clients. 240 stopService(new Intent( 241 "com.example.android.apis.app.REMOTE_SERVICE")); 242 } 243 }; 244 } 245 246 // ---------------------------------------------------------------------- 247 248 /** 249 * Example of binding and unbinding to the remote service. 250 * This demonstrates the implementation of a service which the client will 251 * bind to, interacting with it through an aidl interface.</p> 252 * 253 * <p>Note that this is implemented as an inner class only keep the sample 254 * all together; typically this code would appear in some separate class. 255 */ 256 // BEGIN_INCLUDE(calling_a_service) 257 public static class Binding extends Activity { 258 /** The primary interface we will be calling on the service. */ 259 IRemoteService mService = null; 260 /** Another interface we use on the service. */ 261 ISecondary mSecondaryService = null; 262 263 Button mKillButton; 264 TextView mCallbackText; 265 266 private boolean mIsBound; 267 268 /** 269 * Standard initialization of this activity. Set up the UI, then wait 270 * for the user to poke it before doing anything. 271 */ 272 @Override 273 protected void onCreate(Bundle savedInstanceState) { 274 super.onCreate(savedInstanceState); 275 276 setContentView(R.layout.remote_service_binding); 277 278 // Watch for button clicks. 279 Button button = (Button)findViewById(R.id.bind); 280 button.setOnClickListener(mBindListener); 281 button = (Button)findViewById(R.id.unbind); 282 button.setOnClickListener(mUnbindListener); 283 mKillButton = (Button)findViewById(R.id.kill); 284 mKillButton.setOnClickListener(mKillListener); 285 mKillButton.setEnabled(false); 286 287 mCallbackText = (TextView)findViewById(R.id.callback); 288 mCallbackText.setText("Not attached."); 289 } 290 291 /** 292 * Class for interacting with the main interface of the service. 293 */ 294 private ServiceConnection mConnection = new ServiceConnection() { 295 public void onServiceConnected(ComponentName className, 296 IBinder service) { 297 // This is called when the connection with the service has been 298 // established, giving us the service object we can use to 299 // interact with the service. We are communicating with our 300 // service through an IDL interface, so get a client-side 301 // representation of that from the raw service object. 302 mService = IRemoteService.Stub.asInterface(service); 303 mKillButton.setEnabled(true); 304 mCallbackText.setText("Attached."); 305 306 // We want to monitor the service for as long as we are 307 // connected to it. 308 try { 309 mService.registerCallback(mCallback); 310 } catch (RemoteException e) { 311 // In this case the service has crashed before we could even 312 // do anything with it; we can count on soon being 313 // disconnected (and then reconnected if it can be restarted) 314 // so there is no need to do anything here. 315 } 316 317 // As part of the sample, tell the user what happened. 318 Toast.makeText(Binding.this, R.string.remote_service_connected, 319 Toast.LENGTH_SHORT).show(); 320 } 321 322 public void onServiceDisconnected(ComponentName className) { 323 // This is called when the connection with the service has been 324 // unexpectedly disconnected -- that is, its process crashed. 325 mService = null; 326 mKillButton.setEnabled(false); 327 mCallbackText.setText("Disconnected."); 328 329 // As part of the sample, tell the user what happened. 330 Toast.makeText(Binding.this, R.string.remote_service_disconnected, 331 Toast.LENGTH_SHORT).show(); 332 } 333 }; 334 335 /** 336 * Class for interacting with the secondary interface of the service. 337 */ 338 private ServiceConnection mSecondaryConnection = new ServiceConnection() { 339 public void onServiceConnected(ComponentName className, 340 IBinder service) { 341 // Connecting to a secondary interface is the same as any 342 // other interface. 343 mSecondaryService = ISecondary.Stub.asInterface(service); 344 mKillButton.setEnabled(true); 345 } 346 347 public void onServiceDisconnected(ComponentName className) { 348 mSecondaryService = null; 349 mKillButton.setEnabled(false); 350 } 351 }; 352 353 private OnClickListener mBindListener = new OnClickListener() { 354 public void onClick(View v) { 355 // Establish a couple connections with the service, binding 356 // by interface names. This allows other applications to be 357 // installed that replace the remote service by implementing 358 // the same interface. 359 bindService(new Intent(IRemoteService.class.getName()), 360 mConnection, Context.BIND_AUTO_CREATE); 361 bindService(new Intent(ISecondary.class.getName()), 362 mSecondaryConnection, Context.BIND_AUTO_CREATE); 363 mIsBound = true; 364 mCallbackText.setText("Binding."); 365 } 366 }; 367 368 private OnClickListener mUnbindListener = new OnClickListener() { 369 public void onClick(View v) { 370 if (mIsBound) { 371 // If we have received the service, and hence registered with 372 // it, then now is the time to unregister. 373 if (mService != null) { 374 try { 375 mService.unregisterCallback(mCallback); 376 } catch (RemoteException e) { 377 // There is nothing special we need to do if the service 378 // has crashed. 379 } 380 } 381 382 // Detach our existing connection. 383 unbindService(mConnection); 384 unbindService(mSecondaryConnection); 385 mKillButton.setEnabled(false); 386 mIsBound = false; 387 mCallbackText.setText("Unbinding."); 388 } 389 } 390 }; 391 392 private OnClickListener mKillListener = new OnClickListener() { 393 public void onClick(View v) { 394 // To kill the process hosting our service, we need to know its 395 // PID. Conveniently our service has a call that will return 396 // to us that information. 397 if (mSecondaryService != null) { 398 try { 399 int pid = mSecondaryService.getPid(); 400 // Note that, though this API allows us to request to 401 // kill any process based on its PID, the kernel will 402 // still impose standard restrictions on which PIDs you 403 // are actually able to kill. Typically this means only 404 // the process running your application and any additional 405 // processes created by that app as shown here; packages 406 // sharing a common UID will also be able to kill each 407 // other's processes. 408 Process.killProcess(pid); 409 mCallbackText.setText("Killed service process."); 410 } catch (RemoteException ex) { 411 // Recover gracefully from the process hosting the 412 // server dying. 413 // Just for purposes of the sample, put up a notification. 414 Toast.makeText(Binding.this, 415 R.string.remote_call_failed, 416 Toast.LENGTH_SHORT).show(); 417 } 418 } 419 } 420 }; 421 422 // ---------------------------------------------------------------------- 423 // Code showing how to deal with callbacks. 424 // ---------------------------------------------------------------------- 425 426 /** 427 * This implementation is used to receive callbacks from the remote 428 * service. 429 */ 430 private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() { 431 /** 432 * This is called by the remote service regularly to tell us about 433 * new values. Note that IPC calls are dispatched through a thread 434 * pool running in each process, so the code executing here will 435 * NOT be running in our main thread like most other things -- so, 436 * to update the UI, we need to use a Handler to hop over there. 437 */ 438 public void valueChanged(int value) { 439 mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0)); 440 } 441 }; 442 443 private static final int BUMP_MSG = 1; 444 445 private Handler mHandler = new Handler() { 446 @Override public void handleMessage(Message msg) { 447 switch (msg.what) { 448 case BUMP_MSG: 449 mCallbackText.setText("Received from service: " + msg.arg1); 450 break; 451 default: 452 super.handleMessage(msg); 453 } 454 } 455 456 }; 457 } 458 // END_INCLUDE(calling_a_service) 459 } 460