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 @Override 139 public void onTaskRemoved(Intent rootIntent) { 140 Toast.makeText(this, "Task removed: " + rootIntent, Toast.LENGTH_LONG).show(); 141 } 142 143 private static final int REPORT_MSG = 1; 144 145 /** 146 * Our Handler used to execute operations on the main thread. This is used 147 * to schedule increments of our value. 148 */ 149 private final Handler mHandler = new Handler() { 150 @Override public void handleMessage(Message msg) { 151 switch (msg.what) { 152 153 // It is time to bump the value! 154 case REPORT_MSG: { 155 // Up it goes. 156 int value = ++mValue; 157 158 // Broadcast to all clients the new value. 159 final int N = mCallbacks.beginBroadcast(); 160 for (int i=0; i<N; i++) { 161 try { 162 mCallbacks.getBroadcastItem(i).valueChanged(value); 163 } catch (RemoteException e) { 164 // The RemoteCallbackList will take care of removing 165 // the dead object for us. 166 } 167 } 168 mCallbacks.finishBroadcast(); 169 170 // Repeat every 1 second. 171 sendMessageDelayed(obtainMessage(REPORT_MSG), 1*1000); 172 } break; 173 default: 174 super.handleMessage(msg); 175 } 176 } 177 }; 178 179 /** 180 * Show a notification while this service is running. 181 */ 182 private void showNotification() { 183 // In this sample, we'll use the same text for the ticker and the expanded notification 184 CharSequence text = getText(R.string.remote_service_started); 185 186 // Set the icon, scrolling text and timestamp 187 Notification notification = new Notification(R.drawable.stat_sample, text, 188 System.currentTimeMillis()); 189 190 // The PendingIntent to launch our activity if the user selects this notification 191 PendingIntent contentIntent = PendingIntent.getActivity(this, 0, 192 new Intent(this, Controller.class), 0); 193 194 // Set the info for the views that show in the notification panel. 195 notification.setLatestEventInfo(this, getText(R.string.remote_service_label), 196 text, contentIntent); 197 198 // Send the notification. 199 // We use a string id because it is a unique number. We use it later to cancel. 200 mNM.notify(R.string.remote_service_started, notification); 201 } 202 203 // ---------------------------------------------------------------------- 204 205 /** 206 * <p>Example of explicitly starting and stopping the remove service. 207 * This demonstrates the implementation of a service that runs in a different 208 * process than the rest of the application, which is explicitly started and stopped 209 * as desired.</p> 210 * 211 * <p>Note that this is implemented as an inner class only keep the sample 212 * all together; typically this code would appear in some separate class. 213 */ 214 public static class Controller extends Activity { 215 @Override 216 protected void onCreate(Bundle savedInstanceState) { 217 super.onCreate(savedInstanceState); 218 219 setContentView(R.layout.remote_service_controller); 220 221 // Watch for button clicks. 222 Button button = (Button)findViewById(R.id.start); 223 button.setOnClickListener(mStartListener); 224 button = (Button)findViewById(R.id.stop); 225 button.setOnClickListener(mStopListener); 226 } 227 228 private OnClickListener mStartListener = new OnClickListener() { 229 public void onClick(View v) { 230 // Make sure the service is started. It will continue running 231 // until someone calls stopService(). 232 // We use an action code here, instead of explictly supplying 233 // the component name, so that other packages can replace 234 // the service. 235 startService(new Intent( 236 "com.example.android.apis.app.REMOTE_SERVICE")); 237 } 238 }; 239 240 private OnClickListener mStopListener = new OnClickListener() { 241 public void onClick(View v) { 242 // Cancel a previous call to startService(). Note that the 243 // service will not actually stop at this point if there are 244 // still bound clients. 245 stopService(new Intent( 246 "com.example.android.apis.app.REMOTE_SERVICE")); 247 } 248 }; 249 } 250 251 // ---------------------------------------------------------------------- 252 253 /** 254 * Example of binding and unbinding to the remote service. 255 * This demonstrates the implementation of a service which the client will 256 * bind to, interacting with it through an aidl interface.</p> 257 * 258 * <p>Note that this is implemented as an inner class only keep the sample 259 * all together; typically this code would appear in some separate class. 260 */ 261 // BEGIN_INCLUDE(calling_a_service) 262 public static class Binding extends Activity { 263 /** The primary interface we will be calling on the service. */ 264 IRemoteService mService = null; 265 /** Another interface we use on the service. */ 266 ISecondary mSecondaryService = null; 267 268 Button mKillButton; 269 TextView mCallbackText; 270 271 private boolean mIsBound; 272 273 /** 274 * Standard initialization of this activity. Set up the UI, then wait 275 * for the user to poke it before doing anything. 276 */ 277 @Override 278 protected void onCreate(Bundle savedInstanceState) { 279 super.onCreate(savedInstanceState); 280 281 setContentView(R.layout.remote_service_binding); 282 283 // Watch for button clicks. 284 Button button = (Button)findViewById(R.id.bind); 285 button.setOnClickListener(mBindListener); 286 button = (Button)findViewById(R.id.unbind); 287 button.setOnClickListener(mUnbindListener); 288 mKillButton = (Button)findViewById(R.id.kill); 289 mKillButton.setOnClickListener(mKillListener); 290 mKillButton.setEnabled(false); 291 292 mCallbackText = (TextView)findViewById(R.id.callback); 293 mCallbackText.setText("Not attached."); 294 } 295 296 /** 297 * Class for interacting with the main interface of the service. 298 */ 299 private ServiceConnection mConnection = new ServiceConnection() { 300 public void onServiceConnected(ComponentName className, 301 IBinder service) { 302 // This is called when the connection with the service has been 303 // established, giving us the service object we can use to 304 // interact with the service. We are communicating with our 305 // service through an IDL interface, so get a client-side 306 // representation of that from the raw service object. 307 mService = IRemoteService.Stub.asInterface(service); 308 mKillButton.setEnabled(true); 309 mCallbackText.setText("Attached."); 310 311 // We want to monitor the service for as long as we are 312 // connected to it. 313 try { 314 mService.registerCallback(mCallback); 315 } catch (RemoteException e) { 316 // In this case the service has crashed before we could even 317 // do anything with it; we can count on soon being 318 // disconnected (and then reconnected if it can be restarted) 319 // so there is no need to do anything here. 320 } 321 322 // As part of the sample, tell the user what happened. 323 Toast.makeText(Binding.this, R.string.remote_service_connected, 324 Toast.LENGTH_SHORT).show(); 325 } 326 327 public void onServiceDisconnected(ComponentName className) { 328 // This is called when the connection with the service has been 329 // unexpectedly disconnected -- that is, its process crashed. 330 mService = null; 331 mKillButton.setEnabled(false); 332 mCallbackText.setText("Disconnected."); 333 334 // As part of the sample, tell the user what happened. 335 Toast.makeText(Binding.this, R.string.remote_service_disconnected, 336 Toast.LENGTH_SHORT).show(); 337 } 338 }; 339 340 /** 341 * Class for interacting with the secondary interface of the service. 342 */ 343 private ServiceConnection mSecondaryConnection = new ServiceConnection() { 344 public void onServiceConnected(ComponentName className, 345 IBinder service) { 346 // Connecting to a secondary interface is the same as any 347 // other interface. 348 mSecondaryService = ISecondary.Stub.asInterface(service); 349 mKillButton.setEnabled(true); 350 } 351 352 public void onServiceDisconnected(ComponentName className) { 353 mSecondaryService = null; 354 mKillButton.setEnabled(false); 355 } 356 }; 357 358 private OnClickListener mBindListener = new OnClickListener() { 359 public void onClick(View v) { 360 // Establish a couple connections with the service, binding 361 // by interface names. This allows other applications to be 362 // installed that replace the remote service by implementing 363 // the same interface. 364 bindService(new Intent(IRemoteService.class.getName()), 365 mConnection, Context.BIND_AUTO_CREATE); 366 bindService(new Intent(ISecondary.class.getName()), 367 mSecondaryConnection, Context.BIND_AUTO_CREATE); 368 mIsBound = true; 369 mCallbackText.setText("Binding."); 370 } 371 }; 372 373 private OnClickListener mUnbindListener = new OnClickListener() { 374 public void onClick(View v) { 375 if (mIsBound) { 376 // If we have received the service, and hence registered with 377 // it, then now is the time to unregister. 378 if (mService != null) { 379 try { 380 mService.unregisterCallback(mCallback); 381 } catch (RemoteException e) { 382 // There is nothing special we need to do if the service 383 // has crashed. 384 } 385 } 386 387 // Detach our existing connection. 388 unbindService(mConnection); 389 unbindService(mSecondaryConnection); 390 mKillButton.setEnabled(false); 391 mIsBound = false; 392 mCallbackText.setText("Unbinding."); 393 } 394 } 395 }; 396 397 private OnClickListener mKillListener = new OnClickListener() { 398 public void onClick(View v) { 399 // To kill the process hosting our service, we need to know its 400 // PID. Conveniently our service has a call that will return 401 // to us that information. 402 if (mSecondaryService != null) { 403 try { 404 int pid = mSecondaryService.getPid(); 405 // Note that, though this API allows us to request to 406 // kill any process based on its PID, the kernel will 407 // still impose standard restrictions on which PIDs you 408 // are actually able to kill. Typically this means only 409 // the process running your application and any additional 410 // processes created by that app as shown here; packages 411 // sharing a common UID will also be able to kill each 412 // other's processes. 413 Process.killProcess(pid); 414 mCallbackText.setText("Killed service process."); 415 } catch (RemoteException ex) { 416 // Recover gracefully from the process hosting the 417 // server dying. 418 // Just for purposes of the sample, put up a notification. 419 Toast.makeText(Binding.this, 420 R.string.remote_call_failed, 421 Toast.LENGTH_SHORT).show(); 422 } 423 } 424 } 425 }; 426 427 // ---------------------------------------------------------------------- 428 // Code showing how to deal with callbacks. 429 // ---------------------------------------------------------------------- 430 431 /** 432 * This implementation is used to receive callbacks from the remote 433 * service. 434 */ 435 private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() { 436 /** 437 * This is called by the remote service regularly to tell us about 438 * new values. Note that IPC calls are dispatched through a thread 439 * pool running in each process, so the code executing here will 440 * NOT be running in our main thread like most other things -- so, 441 * to update the UI, we need to use a Handler to hop over there. 442 */ 443 public void valueChanged(int value) { 444 mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0)); 445 } 446 }; 447 448 private static final int BUMP_MSG = 1; 449 450 private Handler mHandler = new Handler() { 451 @Override public void handleMessage(Message msg) { 452 switch (msg.what) { 453 case BUMP_MSG: 454 mCallbackText.setText("Received from service: " + msg.arg1); 455 break; 456 default: 457 super.handleMessage(msg); 458 } 459 } 460 461 }; 462 } 463 // END_INCLUDE(calling_a_service) 464 465 // ---------------------------------------------------------------------- 466 467 /** 468 * Examples of behavior of different bind flags.</p> 469 */ 470 // BEGIN_INCLUDE(calling_a_service) 471 public static class BindingOptions extends Activity { 472 ServiceConnection mCurConnection; 473 TextView mCallbackText; 474 475 class MyServiceConnection implements ServiceConnection { 476 final boolean mUnbindOnDisconnect; 477 478 public MyServiceConnection() { 479 mUnbindOnDisconnect = false; 480 } 481 482 public MyServiceConnection(boolean unbindOnDisconnect) { 483 mUnbindOnDisconnect = unbindOnDisconnect; 484 } 485 486 public void onServiceConnected(ComponentName className, 487 IBinder service) { 488 if (mCurConnection != this) { 489 return; 490 } 491 mCallbackText.setText("Attached."); 492 Toast.makeText(BindingOptions.this, R.string.remote_service_connected, 493 Toast.LENGTH_SHORT).show(); 494 } 495 496 public void onServiceDisconnected(ComponentName className) { 497 if (mCurConnection != this) { 498 return; 499 } 500 mCallbackText.setText("Disconnected."); 501 Toast.makeText(BindingOptions.this, R.string.remote_service_disconnected, 502 Toast.LENGTH_SHORT).show(); 503 if (mUnbindOnDisconnect) { 504 unbindService(this); 505 mCurConnection = null; 506 Toast.makeText(BindingOptions.this, R.string.remote_service_unbind_disconn, 507 Toast.LENGTH_SHORT).show(); 508 } 509 } 510 } 511 512 /** 513 * Standard initialization of this activity. Set up the UI, then wait 514 * for the user to poke it before doing anything. 515 */ 516 @Override 517 protected void onCreate(Bundle savedInstanceState) { 518 super.onCreate(savedInstanceState); 519 520 setContentView(R.layout.remote_binding_options); 521 522 // Watch for button clicks. 523 Button button = (Button)findViewById(R.id.bind_normal); 524 button.setOnClickListener(mBindNormalListener); 525 button = (Button)findViewById(R.id.bind_not_foreground); 526 button.setOnClickListener(mBindNotForegroundListener); 527 button = (Button)findViewById(R.id.bind_above_client); 528 button.setOnClickListener(mBindAboveClientListener); 529 button = (Button)findViewById(R.id.bind_allow_oom); 530 button.setOnClickListener(mBindAllowOomListener); 531 button = (Button)findViewById(R.id.bind_waive_priority); 532 button.setOnClickListener(mBindWaivePriorityListener); 533 button = (Button)findViewById(R.id.bind_important); 534 button.setOnClickListener(mBindImportantListener); 535 button = (Button)findViewById(R.id.bind_with_activity); 536 button.setOnClickListener(mBindWithActivityListener); 537 button = (Button)findViewById(R.id.unbind); 538 button.setOnClickListener(mUnbindListener); 539 540 mCallbackText = (TextView)findViewById(R.id.callback); 541 mCallbackText.setText("Not attached."); 542 } 543 544 private OnClickListener mBindNormalListener = new OnClickListener() { 545 public void onClick(View v) { 546 if (mCurConnection != null) { 547 unbindService(mCurConnection); 548 mCurConnection = null; 549 } 550 ServiceConnection conn = new MyServiceConnection(); 551 if (bindService(new Intent(IRemoteService.class.getName()), 552 conn, Context.BIND_AUTO_CREATE)) { 553 mCurConnection = conn; 554 } 555 } 556 }; 557 558 private OnClickListener mBindNotForegroundListener = new OnClickListener() { 559 public void onClick(View v) { 560 if (mCurConnection != null) { 561 unbindService(mCurConnection); 562 mCurConnection = null; 563 } 564 ServiceConnection conn = new MyServiceConnection(); 565 if (bindService(new Intent(IRemoteService.class.getName()), 566 conn, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND)) { 567 mCurConnection = conn; 568 } 569 } 570 }; 571 572 private OnClickListener mBindAboveClientListener = new OnClickListener() { 573 public void onClick(View v) { 574 if (mCurConnection != null) { 575 unbindService(mCurConnection); 576 mCurConnection = null; 577 } 578 ServiceConnection conn = new MyServiceConnection(); 579 if (bindService(new Intent(IRemoteService.class.getName()), 580 conn, Context.BIND_AUTO_CREATE | Context.BIND_ABOVE_CLIENT)) { 581 mCurConnection = conn; 582 } 583 } 584 }; 585 586 private OnClickListener mBindAllowOomListener = new OnClickListener() { 587 public void onClick(View v) { 588 if (mCurConnection != null) { 589 unbindService(mCurConnection); 590 mCurConnection = null; 591 } 592 ServiceConnection conn = new MyServiceConnection(); 593 if (bindService(new Intent(IRemoteService.class.getName()), 594 conn, Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_OOM_MANAGEMENT)) { 595 mCurConnection = conn; 596 } 597 } 598 }; 599 600 private OnClickListener mBindWaivePriorityListener = new OnClickListener() { 601 public void onClick(View v) { 602 if (mCurConnection != null) { 603 unbindService(mCurConnection); 604 mCurConnection = null; 605 } 606 ServiceConnection conn = new MyServiceConnection(true); 607 if (bindService(new Intent(IRemoteService.class.getName()), 608 conn, Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY)) { 609 mCurConnection = conn; 610 } 611 } 612 }; 613 614 private OnClickListener mBindImportantListener = new OnClickListener() { 615 public void onClick(View v) { 616 if (mCurConnection != null) { 617 unbindService(mCurConnection); 618 mCurConnection = null; 619 } 620 ServiceConnection conn = new MyServiceConnection(); 621 if (bindService(new Intent(IRemoteService.class.getName()), 622 conn, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT)) { 623 mCurConnection = conn; 624 } 625 } 626 }; 627 628 private OnClickListener mBindWithActivityListener = new OnClickListener() { 629 public void onClick(View v) { 630 if (mCurConnection != null) { 631 unbindService(mCurConnection); 632 mCurConnection = null; 633 } 634 ServiceConnection conn = new MyServiceConnection(); 635 if (bindService(new Intent(IRemoteService.class.getName()), 636 conn, Context.BIND_AUTO_CREATE | Context.BIND_ADJUST_WITH_ACTIVITY 637 | Context.BIND_WAIVE_PRIORITY)) { 638 mCurConnection = conn; 639 } 640 } 641 }; 642 643 private OnClickListener mUnbindListener = new OnClickListener() { 644 public void onClick(View v) { 645 if (mCurConnection != null) { 646 unbindService(mCurConnection); 647 mCurConnection = null; 648 } 649 } 650 }; 651 } 652 } 653