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
onCreate()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
onStartCommand(Intent intent, int flags, int startId)84     public int onStartCommand(Intent intent, int flags, int startId) {
85         Log.i("RemoteService", "Received start id " + startId + ": " + intent);
86         return START_NOT_STICKY;
87     }
88 
89     @Override
onDestroy()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
onBind(Intent intent)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
onTaskRemoved(Intent rootIntent)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      */
showNotification()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
onCreate(Bundle savedInstanceState)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
onCreate(Bundle savedInstanceState)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 
MyServiceConnection()487             public MyServiceConnection() {
488                 mUnbindOnDisconnect = false;
489             }
490 
MyServiceConnection(boolean unbindOnDisconnect)491             public MyServiceConnection(boolean unbindOnDisconnect) {
492                 mUnbindOnDisconnect = unbindOnDisconnect;
493             }
494 
onServiceConnected(ComponentName className, IBinder service)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 
onServiceDisconnected(ComponentName className)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
onCreate(Bundle savedInstanceState)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(mBindIntent,
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