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.Intent; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.HandlerThread; 28 import android.os.IBinder; 29 import android.os.Looper; 30 import android.os.Message; 31 import android.os.Process; 32 import android.util.Log; 33 import android.view.View; 34 import android.view.View.OnClickListener; 35 import android.widget.Button; 36 import android.widget.Toast; 37 38 import com.example.android.apis.R; 39 40 /** 41 * This is an example of implementing an application service that runs locally 42 * in the same process as the application. The {@link Controller} 43 * class shows how to interact with the service. 44 * 45 * <p>Notice the use of the {@link NotificationManager} when interesting things 46 * happen in the service. This is generally how background services should 47 * interact with the user, rather than doing something more disruptive such as 48 * calling startActivity(). 49 * 50 * <p>For applications targeting Android 1.5 or beyond, you may want consider 51 * using the {@link android.app.IntentService} class, which takes care of all the 52 * work of creating the extra thread and dispatching commands to it. 53 */ 54 public class ServiceStartArguments extends Service { 55 private NotificationManager mNM; 56 private Intent mInvokeIntent; 57 private volatile Looper mServiceLooper; 58 private volatile ServiceHandler mServiceHandler; 59 60 private final class ServiceHandler extends Handler { ServiceHandler(Looper looper)61 public ServiceHandler(Looper looper) { 62 super(looper); 63 } 64 65 @Override handleMessage(Message msg)66 public void handleMessage(Message msg) { 67 Bundle arguments = (Bundle)msg.obj; 68 69 String txt = arguments.getString("name"); 70 71 Log.i("ServiceStartArguments", "Message: " + msg + ", " 72 + arguments.getString("name")); 73 74 if ((msg.arg2&Service.START_FLAG_REDELIVERY) == 0) { 75 txt = "New cmd #" + msg.arg1 + ": " + txt; 76 } else { 77 txt = "Re-delivered #" + msg.arg1 + ": " + txt; 78 } 79 80 showNotification(txt); 81 82 // Normally we would do some work here... for our sample, we will 83 // just sleep for 5 seconds. 84 long endTime = System.currentTimeMillis() + 5*1000; 85 while (System.currentTimeMillis() < endTime) { 86 synchronized (this) { 87 try { 88 wait(endTime - System.currentTimeMillis()); 89 } catch (Exception e) { 90 } 91 } 92 } 93 94 hideNotification(); 95 96 Log.i("ServiceStartArguments", "Done with #" + msg.arg1); 97 stopSelf(msg.arg1); 98 } 99 100 }; 101 102 @Override onCreate()103 public void onCreate() { 104 mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); 105 106 Toast.makeText(this, R.string.service_created, 107 Toast.LENGTH_SHORT).show(); 108 109 // This is who should be launched if the user selects our persistent 110 // notification. 111 mInvokeIntent = new Intent(this, Controller.class); 112 113 // Start up the thread running the service. Note that we create a 114 // separate thread because the service normally runs in the process's 115 // main thread, which we don't want to block. We also make it 116 // background priority so CPU-intensive work will not disrupt our UI. 117 HandlerThread thread = new HandlerThread("ServiceStartArguments", 118 Process.THREAD_PRIORITY_BACKGROUND); 119 thread.start(); 120 121 mServiceLooper = thread.getLooper(); 122 mServiceHandler = new ServiceHandler(mServiceLooper); 123 } 124 125 @Override onStartCommand(Intent intent, int flags, int startId)126 public int onStartCommand(Intent intent, int flags, int startId) { 127 Log.i("ServiceStartArguments", 128 "Starting #" + startId + ": " + intent.getExtras()); 129 Message msg = mServiceHandler.obtainMessage(); 130 msg.arg1 = startId; 131 msg.arg2 = flags; 132 msg.obj = intent.getExtras(); 133 mServiceHandler.sendMessage(msg); 134 Log.i("ServiceStartArguments", "Sending: " + msg); 135 136 // For the start fail button, we will simulate the process dying 137 // for some reason in onStartCommand(). 138 if (intent.getBooleanExtra("fail", false)) { 139 // Don't do this if we are in a retry... the system will 140 // eventually give up if we keep crashing. 141 if ((flags&START_FLAG_RETRY) == 0) { 142 // Since the process hasn't finished handling the command, 143 // it will be restarted with the command again, regardless of 144 // whether we return START_REDELIVER_INTENT. 145 Process.killProcess(Process.myPid()); 146 } 147 } 148 149 // Normally we would consistently return one kind of result... 150 // however, here we will select between these two, so you can see 151 // how they impact the behavior. Try killing the process while it 152 // is in the middle of executing the different commands. 153 return intent.getBooleanExtra("redeliver", false) 154 ? START_REDELIVER_INTENT : START_NOT_STICKY; 155 } 156 157 @Override onDestroy()158 public void onDestroy() { 159 mServiceLooper.quit(); 160 161 hideNotification(); 162 163 // Tell the user we stopped. 164 Toast.makeText(ServiceStartArguments.this, R.string.service_destroyed, 165 Toast.LENGTH_SHORT).show(); 166 } 167 168 @Override onBind(Intent intent)169 public IBinder onBind(Intent intent) { 170 return null; 171 } 172 173 /** 174 * Show a notification while this service is running. 175 */ showNotification(String text)176 private void showNotification(String text) { 177 // The PendingIntent to launch our activity if the user selects this notification 178 PendingIntent contentIntent = PendingIntent.getActivity(this, 0, 179 new Intent(this, Controller.class), 0); 180 181 // Set the info for the views that show in the notification panel. 182 Notification.Builder noteBuilder = new Notification.Builder(this) 183 .setSmallIcon(R.drawable.stat_sample) // the status icon 184 .setTicker(text) // the status text 185 .setWhen(System.currentTimeMillis()) // the time stamp 186 .setContentTitle(getText(R.string.service_start_arguments_label)) // the label 187 .setContentText(text) // the contents of the entry 188 .setContentIntent(contentIntent); // The intent to send when the entry is clicked 189 190 // We show this for as long as our service is processing a command. 191 noteBuilder.setOngoing(true); 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.service_created, noteBuilder.build()); 196 } 197 hideNotification()198 private void hideNotification() { 199 mNM.cancel(R.string.service_created); 200 } 201 202 // ---------------------------------------------------------------------- 203 204 /** 205 * Example of explicitly starting the {@link ServiceStartArguments}. 206 * 207 * <p>Note that this is implemented as an inner class only keep the sample 208 * all together; typically this code would appear in some separate class. 209 */ 210 public static class Controller extends Activity { 211 @Override onCreate(Bundle savedInstanceState)212 protected void onCreate(Bundle savedInstanceState) { 213 super.onCreate(savedInstanceState); 214 215 setContentView(R.layout.service_start_arguments_controller); 216 217 // Watch for button clicks. 218 Button button = (Button)findViewById(R.id.start1); 219 button.setOnClickListener(mStart1Listener); 220 button = (Button)findViewById(R.id.start2); 221 button.setOnClickListener(mStart2Listener); 222 button = (Button)findViewById(R.id.start3); 223 button.setOnClickListener(mStart3Listener); 224 button = (Button)findViewById(R.id.startfail); 225 button.setOnClickListener(mStartFailListener); 226 button = (Button)findViewById(R.id.kill); 227 button.setOnClickListener(mKillListener); 228 } 229 230 private OnClickListener mStart1Listener = new OnClickListener() { 231 public void onClick(View v) { 232 startService(new Intent(Controller.this, 233 ServiceStartArguments.class) 234 .putExtra("name", "One")); 235 } 236 }; 237 238 private OnClickListener mStart2Listener = new OnClickListener() { 239 public void onClick(View v) { 240 startService(new Intent(Controller.this, 241 ServiceStartArguments.class) 242 .putExtra("name", "Two")); 243 } 244 }; 245 246 private OnClickListener mStart3Listener = new OnClickListener() { 247 public void onClick(View v) { 248 startService(new Intent(Controller.this, 249 ServiceStartArguments.class) 250 .putExtra("name", "Three") 251 .putExtra("redeliver", true)); 252 } 253 }; 254 255 private OnClickListener mStartFailListener = new OnClickListener() { 256 public void onClick(View v) { 257 startService(new Intent(Controller.this, 258 ServiceStartArguments.class) 259 .putExtra("name", "Failure") 260 .putExtra("fail", true)); 261 } 262 }; 263 264 private OnClickListener mKillListener = new OnClickListener() { 265 public void onClick(View v) { 266 // This is to simulate the service being killed while it is 267 // running in the background. 268 Process.killProcess(Process.myPid()); 269 } 270 }; 271 } 272 } 273 274