1/*
2 * Copyright (C) 2009 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
17package com.example.android.apis.app;
18
19import android.app.Activity;
20import android.app.Notification;
21import android.app.NotificationManager;
22import android.app.PendingIntent;
23import android.app.Service;
24import android.content.Intent;
25import android.os.Bundle;
26import android.os.IBinder;
27import android.util.Log;
28import android.view.View;
29import android.view.View.OnClickListener;
30import android.widget.Button;
31
32import java.lang.reflect.InvocationTargetException;
33import java.lang.reflect.Method;
34
35// Need the following import to get access to the app resources, since this
36// class is in a sub-package.
37import com.example.android.apis.R;
38
39/**
40 * This is an example of implementing an application service that can
41 * run in the "foreground".  It shows how to code this to work well by using
42 * the improved Android 2.0 APIs when available and otherwise falling back
43 * to the original APIs.  Yes: you can take this exact code, compile it
44 * against the Android 2.0 SDK, and it will against everything down to
45 * Android 1.0.
46 */
47public class ForegroundService extends Service {
48    static final String ACTION_FOREGROUND = "com.example.android.apis.FOREGROUND";
49    static final String ACTION_BACKGROUND = "com.example.android.apis.BACKGROUND";
50
51 // BEGIN_INCLUDE(foreground_compatibility)
52    private static final Class<?>[] mSetForegroundSignature = new Class[] {
53        boolean.class};
54    private static final Class<?>[] mStartForegroundSignature = new Class[] {
55        int.class, Notification.class};
56    private static final Class<?>[] mStopForegroundSignature = new Class[] {
57        boolean.class};
58
59    private NotificationManager mNM;
60    private Method mSetForeground;
61    private Method mStartForeground;
62    private Method mStopForeground;
63    private Object[] mSetForegroundArgs = new Object[1];
64    private Object[] mStartForegroundArgs = new Object[2];
65    private Object[] mStopForegroundArgs = new Object[1];
66
67    void invokeMethod(Method method, Object[] args) {
68        try {
69            method.invoke(this, args);
70        } catch (InvocationTargetException e) {
71            // Should not happen.
72            Log.w("ApiDemos", "Unable to invoke method", e);
73        } catch (IllegalAccessException e) {
74            // Should not happen.
75            Log.w("ApiDemos", "Unable to invoke method", e);
76        }
77    }
78
79    /**
80     * This is a wrapper around the new startForeground method, using the older
81     * APIs if it is not available.
82     */
83    void startForegroundCompat(int id, Notification notification) {
84        // If we have the new startForeground API, then use it.
85        if (mStartForeground != null) {
86            mStartForegroundArgs[0] = Integer.valueOf(id);
87            mStartForegroundArgs[1] = notification;
88            invokeMethod(mStartForeground, mStartForegroundArgs);
89            return;
90        }
91
92        // Fall back on the old API.
93        mSetForegroundArgs[0] = Boolean.TRUE;
94        invokeMethod(mSetForeground, mSetForegroundArgs);
95        mNM.notify(id, notification);
96    }
97
98    /**
99     * This is a wrapper around the new stopForeground method, using the older
100     * APIs if it is not available.
101     */
102    void stopForegroundCompat(int id) {
103        // If we have the new stopForeground API, then use it.
104        if (mStopForeground != null) {
105            mStopForegroundArgs[0] = Boolean.TRUE;
106            invokeMethod(mStopForeground, mStopForegroundArgs);
107            return;
108        }
109
110        // Fall back on the old API.  Note to cancel BEFORE changing the
111        // foreground state, since we could be killed at that point.
112        mNM.cancel(id);
113        mSetForegroundArgs[0] = Boolean.FALSE;
114        invokeMethod(mSetForeground, mSetForegroundArgs);
115    }
116
117    @Override
118    public void onCreate() {
119        mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
120        try {
121            mStartForeground = getClass().getMethod("startForeground",
122                    mStartForegroundSignature);
123            mStopForeground = getClass().getMethod("stopForeground",
124                    mStopForegroundSignature);
125            return;
126        } catch (NoSuchMethodException e) {
127            // Running on an older platform.
128            mStartForeground = mStopForeground = null;
129        }
130        try {
131            mSetForeground = getClass().getMethod("setForeground",
132                    mSetForegroundSignature);
133        } catch (NoSuchMethodException e) {
134            throw new IllegalStateException(
135                    "OS doesn't have Service.startForeground OR Service.setForeground!");
136        }
137    }
138
139    @Override
140    public void onDestroy() {
141        // Make sure our notification is gone.
142        stopForegroundCompat(R.string.foreground_service_started);
143    }
144// END_INCLUDE(foreground_compatibility)
145
146// BEGIN_INCLUDE(start_compatibility)
147    // This is the old onStart method that will be called on the pre-2.0
148    // platform.  On 2.0 or later we override onStartCommand() so this
149    // method will not be called.
150    @Override
151    public void onStart(Intent intent, int startId) {
152        handleCommand(intent);
153    }
154
155    @Override
156    public int onStartCommand(Intent intent, int flags, int startId) {
157        handleCommand(intent);
158        // We want this service to continue running until it is explicitly
159        // stopped, so return sticky.
160        return START_STICKY;
161    }
162// END_INCLUDE(start_compatibility)
163
164    void handleCommand(Intent intent) {
165        if (ACTION_FOREGROUND.equals(intent.getAction())) {
166            // In this sample, we'll use the same text for the ticker and the expanded notification
167            CharSequence text = getText(R.string.foreground_service_started);
168
169            // Set the icon, scrolling text and timestamp
170            Notification notification = new Notification(R.drawable.stat_sample, text,
171                    System.currentTimeMillis());
172
173            // The PendingIntent to launch our activity if the user selects this notification
174            PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
175                    new Intent(this, Controller.class), 0);
176
177            // Set the info for the views that show in the notification panel.
178            notification.setLatestEventInfo(this, getText(R.string.local_service_label),
179                           text, contentIntent);
180
181            startForegroundCompat(R.string.foreground_service_started, notification);
182
183        } else if (ACTION_BACKGROUND.equals(intent.getAction())) {
184            stopForegroundCompat(R.string.foreground_service_started);
185        }
186    }
187
188    @Override
189    public IBinder onBind(Intent intent) {
190        return null;
191    }
192
193    // ----------------------------------------------------------------------
194
195    /**
196     * <p>Example of explicitly starting and stopping the {@link ForegroundService}.
197     *
198     * <p>Note that this is implemented as an inner class only keep the sample
199     * all together; typically this code would appear in some separate class.
200     */
201    public static class Controller extends Activity {
202        @Override
203        protected void onCreate(Bundle savedInstanceState) {
204            super.onCreate(savedInstanceState);
205
206            setContentView(R.layout.foreground_service_controller);
207
208            // Watch for button clicks.
209            Button button = (Button)findViewById(R.id.start_foreground);
210            button.setOnClickListener(mForegroundListener);
211            button = (Button)findViewById(R.id.start_background);
212            button.setOnClickListener(mBackgroundListener);
213            button = (Button)findViewById(R.id.stop);
214            button.setOnClickListener(mStopListener);
215        }
216
217        private OnClickListener mForegroundListener = new OnClickListener() {
218            public void onClick(View v) {
219                Intent intent = new Intent(ForegroundService.ACTION_FOREGROUND);
220                intent.setClass(Controller.this, ForegroundService.class);
221                startService(intent);
222            }
223        };
224
225        private OnClickListener mBackgroundListener = new OnClickListener() {
226            public void onClick(View v) {
227                Intent intent = new Intent(ForegroundService.ACTION_BACKGROUND);
228                intent.setClass(Controller.this, ForegroundService.class);
229                startService(intent);
230            }
231        };
232
233        private OnClickListener mStopListener = new OnClickListener() {
234            public void onClick(View v) {
235                stopService(new Intent(Controller.this,
236                        ForegroundService.class));
237            }
238        };
239    }
240}
241