blob: d59848db19ac1d2cf28e3b59e2302c9881ba05c4 [file] [log] [blame]
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -08001/*
2 * Copyright (C) 2014 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.android.camera.processing;
18
Sascha Haeberling4a400d72014-03-21 10:41:04 -070019import android.app.Notification;
20import android.app.NotificationManager;
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -080021import android.app.Service;
Sascha Haeberling58acf0d2014-03-20 13:27:14 -070022import android.content.BroadcastReceiver;
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -080023import android.content.Context;
24import android.content.Intent;
Sascha Haeberling58acf0d2014-03-20 13:27:14 -070025import android.content.IntentFilter;
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -080026import android.os.IBinder;
27import android.os.PowerManager;
28import android.os.PowerManager.WakeLock;
29import android.os.Process;
Sascha Haeberling58acf0d2014-03-20 13:27:14 -070030import android.support.v4.content.LocalBroadcastManager;
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -080031
32import com.android.camera.app.CameraApp;
33import com.android.camera.app.CameraServices;
Angus Kong2bca2102014-03-11 16:27:30 -070034import com.android.camera.debug.Log;
Seth Raphael2a978f22014-03-10 16:15:13 -070035import com.android.camera.session.CaptureSession;
Sascha Haeberling4a400d72014-03-21 10:41:04 -070036import com.android.camera.session.CaptureSession.ProgressListener;
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -080037import com.android.camera.session.CaptureSessionManager;
Sascha Haeberling4a400d72014-03-21 10:41:04 -070038import com.android.camera2.R;
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -080039
Sascha Haeberling58acf0d2014-03-20 13:27:14 -070040import java.util.concurrent.locks.Lock;
41import java.util.concurrent.locks.ReentrantLock;
42
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -080043/**
44 * A service that processes a {@code ProcessingTask}. The service uses a fifo
45 * queue so that only one {@code ProcessingTask} is processed at a time.
46 * <p>
47 * The service is meant to be called via {@code ProcessingService.addTask},
48 * which takes care of starting the service and enqueueing the
49 * {@code ProcessingTask} task:
50 *
51 * <pre>
52 * {@code
53 * ProcessingTask task = new MyProcessingTask(...);
54 * ProcessingService.addTask(task);
55 * }
56 * </pre>
57 */
Sascha Haeberling4a400d72014-03-21 10:41:04 -070058public class ProcessingService extends Service implements ProgressListener {
Sascha Haeberling58acf0d2014-03-20 13:27:14 -070059 /**
60 * Class used to receive broadcast and control the service accordingly.
61 */
62 public class ServiceController extends BroadcastReceiver {
63 @Override
64 public void onReceive(Context context, Intent intent) {
65 if (intent.getAction() == ACTION_PAUSE_PROCESSING_SERVICE) {
66 ProcessingService.this.pause();
67 } else if (intent.getAction() == ACTION_RESUME_PROCESSING_SERVICE) {
68 ProcessingService.this.resume();
69 }
70 }
71 }
72
Angus Kong2bca2102014-03-11 16:27:30 -070073 private static final Log.Tag TAG = new Log.Tag("ProcessingService");
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -080074 private static final int THREAD_PRIORITY = Process.THREAD_PRIORITY_DISPLAY;
Sascha Haeberling4a400d72014-03-21 10:41:04 -070075 private static final int CAMERA_NOTIFICATION_ID = 2;
76 private Notification.Builder mNotificationBuilder;
77 private NotificationManager mNotificationManager;
Sascha Haeberling58acf0d2014-03-20 13:27:14 -070078
79 /** Sending this broadcast intent will cause the processing to pause. */
80 public static final String ACTION_PAUSE_PROCESSING_SERVICE =
81 "com.android.camera.processing.PAUSE";
82 /**
83 * Sending this broadcast intent will cause the processing to resume after
84 * it has been paused.
85 */
86 public static final String ACTION_RESUME_PROCESSING_SERVICE =
87 "com.android.camera.processing.RESUME";
88
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -080089 private WakeLock mWakeLock;
Sascha Haeberling58acf0d2014-03-20 13:27:14 -070090 private final ServiceController mServiceController = new ServiceController();
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -080091
92 /** Manages the capture session. */
93 private CaptureSessionManager mSessionManager;
94
95 private ProcessingServiceManager mProcessingServiceManager;
96 private Thread mProcessingThread;
Sascha Haeberling58acf0d2014-03-20 13:27:14 -070097 private volatile boolean mPaused = false;
98 private ProcessingTask mCurrentTask;
99 private final Lock mSuspendStatusLock = new ReentrantLock();
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -0800100
101 @Override
102 public void onCreate() {
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -0800103 mProcessingServiceManager = ProcessingServiceManager.getInstance();
104 mSessionManager = getServices().getCaptureSessionManager();
105
106 // Keep CPU awake while allowing screen and keyboard to switch off.
107 PowerManager powerManager = (PowerManager) getSystemService(
108 Context.POWER_SERVICE);
Angus Kong2bca2102014-03-11 16:27:30 -0700109 mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG.toString());
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -0800110 mWakeLock.acquire();
Sascha Haeberling58acf0d2014-03-20 13:27:14 -0700111
112 IntentFilter intentFilter = new IntentFilter();
113 intentFilter.addAction(ACTION_PAUSE_PROCESSING_SERVICE);
114 intentFilter.addAction(ACTION_RESUME_PROCESSING_SERVICE);
115 LocalBroadcastManager.getInstance(this).registerReceiver(mServiceController, intentFilter);
Sascha Haeberling4a400d72014-03-21 10:41:04 -0700116 mNotificationBuilder = createInProgressNotificationBuilder();
117 mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -0800118 }
119
120 @Override
121 public void onDestroy() {
122 Log.d(TAG, "Shutting down");
123
124 // Tell the manager that we're shutting down, so in case new tasks are
125 // enqueued, we a new service needs to be started.
Carlos Hernandez9f834952014-03-24 13:38:17 -0700126 mProcessingServiceManager.notifyServiceFinished();
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -0800127
128 // TODO: Cancel session in progress...
129
130 // Unlock the power manager, i.e. let power management kick in if
131 // needed.
132 if (mWakeLock.isHeld()) {
133 mWakeLock.release();
134 }
Sascha Haeberling58acf0d2014-03-20 13:27:14 -0700135 LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceController);
Sascha Haeberling4a400d72014-03-21 10:41:04 -0700136 stopForeground(true);
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -0800137 }
138
139 @Override
140 public int onStartCommand(Intent intent, int flags, int startId) {
Sascha Haeberling4a5b9e02014-03-24 09:23:14 -0700141 Log.d(TAG, "Starting in foreground.");
142
Sascha Haeberling4a400d72014-03-21 10:41:04 -0700143 // We need to start this service in foreground so that it's not getting
144 // killed easily when memory pressure is building up.
145 startForeground(CAMERA_NOTIFICATION_ID, mNotificationBuilder.build());
Sascha Haeberling58acf0d2014-03-20 13:27:14 -0700146
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -0800147 asyncProcessAllTasksAndShutdown();
148
149 // We want this service to continue running until it is explicitly
150 // stopped, so return sticky.
151 return START_STICKY;
152 }
153
154 @Override
155 public IBinder onBind(Intent intent) {
156 // We don't provide binding, so return null.
157 return null;
158 }
159
Sascha Haeberling58acf0d2014-03-20 13:27:14 -0700160 private void pause() {
Sascha Haeberling4a5b9e02014-03-24 09:23:14 -0700161 Log.d(TAG, "Pausing");
Sascha Haeberling58acf0d2014-03-20 13:27:14 -0700162 try {
163 mSuspendStatusLock.lock();
164 mPaused = true;
165 if (mCurrentTask != null) {
166 mCurrentTask.suspend();
167 }
168 } finally {
169 mSuspendStatusLock.unlock();
170 }
171 }
172
173 private void resume() {
Sascha Haeberling4a5b9e02014-03-24 09:23:14 -0700174 Log.d(TAG, "Resuming");
Sascha Haeberling58acf0d2014-03-20 13:27:14 -0700175 try {
176 mSuspendStatusLock.lock();
177 mPaused = false;
178 if (mCurrentTask != null) {
179 mCurrentTask.resume();
180 }
181 } finally {
182 mSuspendStatusLock.unlock();
183 }
184 }
185
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -0800186 /**
187 * Starts a thread to process all tasks. When no more tasks are in the
188 * queue, it exits the thread and shuts down the service.
189 */
190 private void asyncProcessAllTasksAndShutdown() {
191 if (mProcessingThread != null) {
192 return;
193 }
Sascha Haeberling58acf0d2014-03-20 13:27:14 -0700194 mProcessingThread = new Thread("CameraProcessingThread") {
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -0800195 @Override
196 public void run() {
197 // Set the thread priority
198 android.os.Process.setThreadPriority(THREAD_PRIORITY);
199
200 ProcessingTask task;
201 while ((task = mProcessingServiceManager.popNextSession()) != null) {
Sascha Haeberling58acf0d2014-03-20 13:27:14 -0700202 mCurrentTask = task;
203 try {
204 mSuspendStatusLock.lock();
205 if (mPaused) {
206 mCurrentTask.suspend();
207 }
208 } finally {
209 mSuspendStatusLock.unlock();
210 }
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -0800211 processAndNotify(task);
212 }
213 stopSelf();
214 }
215 };
216 mProcessingThread.start();
217 }
218
219 /**
220 * Processes a {@code ProcessingTask} and updates the notification bar.
221 */
222 void processAndNotify(ProcessingTask task) {
223 if (task == null) {
224 Log.e(TAG, "Reference to ProcessingTask is null");
225 return;
226 }
Seth Raphael2a978f22014-03-10 16:15:13 -0700227 CaptureSession session = task.getSession();
228 if (session == null) {
229 session = mSessionManager.createNewSession(task.getName(), task.getLocation());
230 }
Sascha Haeberling4a400d72014-03-21 10:41:04 -0700231 resetNotification();
232
233 // Adding the listener also causes it to get called for the session's
234 // current status message and percent completed.
235 session.addProgressListener(this);
236
Sascha Haeberling58acf0d2014-03-20 13:27:14 -0700237 System.gc();
Sascha Haeberling4a5b9e02014-03-24 09:23:14 -0700238 Log.d(TAG, "Processing start");
Seth Raphael2a978f22014-03-10 16:15:13 -0700239 task.process(this, getServices(), session);
Sascha Haeberling4a5b9e02014-03-24 09:23:14 -0700240 Log.d(TAG, "Processing done");
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -0800241 }
242
Sascha Haeberling4a400d72014-03-21 10:41:04 -0700243 private void resetNotification() {
244 mNotificationBuilder.setContentText("…").setProgress(100, 0, false);
245 postNotification();
246 }
247
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -0800248 /**
249 * Returns the common camera services.
250 */
251 private CameraServices getServices() {
252 return (CameraApp) this.getApplication();
253 }
Sascha Haeberling4a400d72014-03-21 10:41:04 -0700254
255 private void postNotification() {
256 mNotificationManager.notify(CAMERA_NOTIFICATION_ID, mNotificationBuilder.build());
257 }
258
259 /**
260 * Creates a notification to indicate that a computation is in progress.
261 */
262 private Notification.Builder createInProgressNotificationBuilder() {
263 return new Notification.Builder(this)
264 .setSmallIcon(R.drawable.ic_notification)
265 .setWhen(System.currentTimeMillis())
266 .setOngoing(true)
267 .setContentTitle(this.getText(R.string.app_name));
268 }
269
270 @Override
271 public void onProgressChanged(int progress) {
272 mNotificationBuilder.setProgress(100, progress, false);
273 postNotification();
274 }
275
276 @Override
277 public void onStatusMessageChanged(CharSequence message) {
278 mNotificationBuilder.setContentText(message);
279 postNotification();
280 }
Sascha Haeberlingdd2d9e62014-01-07 09:36:14 -0800281}