1 /* 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 17 package com.android.camera.processing; 18 19 import android.app.Notification; 20 import android.app.NotificationManager; 21 import android.app.Service; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.os.IBinder; 27 import android.os.PowerManager; 28 import android.os.PowerManager.WakeLock; 29 import android.os.Process; 30 import android.support.v4.content.LocalBroadcastManager; 31 32 import com.android.camera.app.CameraApp; 33 import com.android.camera.app.CameraServices; 34 import com.android.camera.debug.Log; 35 import com.android.camera.session.CaptureSession; 36 import com.android.camera.session.CaptureSession.ProgressListener; 37 import com.android.camera.session.CaptureSessionManager; 38 import com.android.camera2.R; 39 40 import java.util.concurrent.locks.Lock; 41 import java.util.concurrent.locks.ReentrantLock; 42 43 /** 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 */ 58 public class ProcessingService extends Service implements ProgressListener { 59 /** 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 73 private static final Log.Tag TAG = new Log.Tag("ProcessingService"); 74 private static final int THREAD_PRIORITY = Process.THREAD_PRIORITY_DISPLAY; 75 private static final int CAMERA_NOTIFICATION_ID = 2; 76 private Notification.Builder mNotificationBuilder; 77 private NotificationManager mNotificationManager; 78 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 89 private WakeLock mWakeLock; 90 private final ServiceController mServiceController = new ServiceController(); 91 92 /** Manages the capture session. */ 93 private CaptureSessionManager mSessionManager; 94 95 private ProcessingServiceManager mProcessingServiceManager; 96 private Thread mProcessingThread; 97 private volatile boolean mPaused = false; 98 private ProcessingTask mCurrentTask; 99 private final Lock mSuspendStatusLock = new ReentrantLock(); 100 101 @Override 102 public void onCreate() { 103 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); 109 mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG.toString()); 110 mWakeLock.acquire(); 111 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); 116 mNotificationBuilder = createInProgressNotificationBuilder(); 117 mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 118 } 119 120 @Override 121 public void onDestroy() { 122 Log.d(TAG, "Shutting down"); 123 // TODO: Cancel session in progress... 124 125 // Unlock the power manager, i.e. let power management kick in if 126 // needed. 127 if (mWakeLock.isHeld()) { 128 mWakeLock.release(); 129 } 130 LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceController); 131 stopForeground(true); 132 } 133 134 @Override 135 public int onStartCommand(Intent intent, int flags, int startId) { 136 Log.d(TAG, "Starting in foreground."); 137 138 // We need to start this service in foreground so that it's not getting 139 // killed easily when memory pressure is building up. 140 startForeground(CAMERA_NOTIFICATION_ID, mNotificationBuilder.build()); 141 142 asyncProcessAllTasksAndShutdown(); 143 144 // We want this service to continue running until it is explicitly 145 // stopped, so return sticky. 146 return START_STICKY; 147 } 148 149 @Override 150 public IBinder onBind(Intent intent) { 151 // We don't provide binding, so return null. 152 return null; 153 } 154 155 private void pause() { 156 Log.d(TAG, "Pausing"); 157 try { 158 mSuspendStatusLock.lock(); 159 mPaused = true; 160 if (mCurrentTask != null) { 161 mCurrentTask.suspend(); 162 } 163 } finally { 164 mSuspendStatusLock.unlock(); 165 } 166 } 167 168 private void resume() { 169 Log.d(TAG, "Resuming"); 170 try { 171 mSuspendStatusLock.lock(); 172 mPaused = false; 173 if (mCurrentTask != null) { 174 mCurrentTask.resume(); 175 } 176 } finally { 177 mSuspendStatusLock.unlock(); 178 } 179 } 180 181 /** 182 * Starts a thread to process all tasks. When no more tasks are in the 183 * queue, it exits the thread and shuts down the service. 184 */ 185 private void asyncProcessAllTasksAndShutdown() { 186 if (mProcessingThread != null) { 187 return; 188 } 189 mProcessingThread = new Thread("CameraProcessingThread") { 190 @Override 191 public void run() { 192 // Set the thread priority 193 android.os.Process.setThreadPriority(THREAD_PRIORITY); 194 195 ProcessingTask task; 196 while ((task = mProcessingServiceManager.popNextSession()) != null) { 197 mCurrentTask = task; 198 try { 199 mSuspendStatusLock.lock(); 200 if (mPaused) { 201 mCurrentTask.suspend(); 202 } 203 } finally { 204 mSuspendStatusLock.unlock(); 205 } 206 processAndNotify(task); 207 } 208 stopSelf(); 209 } 210 }; 211 mProcessingThread.start(); 212 } 213 214 /** 215 * Processes a {@code ProcessingTask} and updates the notification bar. 216 */ 217 void processAndNotify(ProcessingTask task) { 218 if (task == null) { 219 Log.e(TAG, "Reference to ProcessingTask is null"); 220 return; 221 } 222 CaptureSession session = task.getSession(); 223 if (session == null) { 224 // TODO: Timestamp is not required right now, refactor this to make it clearer. 225 session = mSessionManager.createNewSession(task.getName(), 0, task.getLocation()); 226 } 227 resetNotification(); 228 229 // Adding the listener also causes it to get called for the session's 230 // current status message and percent completed. 231 session.addProgressListener(this); 232 233 System.gc(); 234 Log.d(TAG, "Processing start"); 235 task.process(this, getServices(), session); 236 Log.d(TAG, "Processing done"); 237 } 238 239 private void resetNotification() { 240 mNotificationBuilder.setContentText("").setProgress(100, 0, false); 241 postNotification(); 242 } 243 244 /** 245 * Returns the common camera services. 246 */ 247 private CameraServices getServices() { 248 return (CameraApp) this.getApplication(); 249 } 250 251 private void postNotification() { 252 mNotificationManager.notify(CAMERA_NOTIFICATION_ID, mNotificationBuilder.build()); 253 } 254 255 /** 256 * Creates a notification to indicate that a computation is in progress. 257 */ 258 private Notification.Builder createInProgressNotificationBuilder() { 259 return new Notification.Builder(this) 260 .setSmallIcon(R.drawable.ic_notification) 261 .setWhen(System.currentTimeMillis()) 262 .setOngoing(true) 263 .setContentTitle(this.getText(R.string.app_name)); 264 } 265 266 @Override 267 public void onProgressChanged(int progress) { 268 mNotificationBuilder.setProgress(100, progress, false); 269 postNotification(); 270 } 271 272 @Override 273 public void onStatusMessageChanged(CharSequence message) { 274 mNotificationBuilder.setContentText(message); 275 postNotification(); 276 } 277 } 278