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