Merge "Move Refocus' processing service to common code." into gb-ub-photos-denali
diff --git a/src/com/android/camera/app/CameraApp.java b/src/com/android/camera/app/CameraApp.java
index 07cd322..bb27cc9 100644
--- a/src/com/android/camera/app/CameraApp.java
+++ b/src/com/android/camera/app/CameraApp.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 
 import com.android.camera.MediaSaverImpl;
+import com.android.camera.processing.ProcessingServiceManager;
 import com.android.camera.session.CaptureSessionManager;
 import com.android.camera.session.CaptureSessionManagerImpl;
 import com.android.camera.session.PlaceholderManager;
@@ -45,6 +46,8 @@
         CameraUtil.initialize(this);
 
         Context context = getApplicationContext();
+        ProcessingServiceManager.initSingleton(context);
+
         mMediaSaver = new MediaSaverImpl();
         mNotificationManager = new ProcessingNotificationManager(this);
         mPlaceHolderManager = new PlaceholderManager(context);
diff --git a/src/com/android/camera/processing/ProcessingService.java b/src/com/android/camera/processing/ProcessingService.java
new file mode 100644
index 0000000..75c030f
--- /dev/null
+++ b/src/com/android/camera/processing/ProcessingService.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.processing;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.Process;
+import android.util.Log;
+
+import com.android.camera.app.CameraApp;
+import com.android.camera.app.CameraServices;
+import com.android.camera.session.CaptureSessionManager;
+
+/**
+ * A service that processes a {@code ProcessingTask}. The service uses a fifo
+ * queue so that only one {@code ProcessingTask} is processed at a time.
+ * <p>
+ * The service is meant to be called via {@code ProcessingService.addTask},
+ * which takes care of starting the service and enqueueing the
+ * {@code ProcessingTask} task:
+ *
+ * <pre>
+ * {@code
+ * ProcessingTask task = new MyProcessingTask(...);
+ * ProcessingService.addTask(task);
+ * }
+ * </pre>
+ */
+public class ProcessingService extends Service {
+    private static final String TAG = "ProcessingService";
+    private static final int THREAD_PRIORITY = Process.THREAD_PRIORITY_DISPLAY;
+    private WakeLock mWakeLock;
+
+    /** Manages the capture session. */
+    private CaptureSessionManager mSessionManager;
+
+    private ProcessingServiceManager mProcessingServiceManager;
+    private Thread mProcessingThread;
+
+    @Override
+    public void onCreate() {
+        Log.d(TAG, "Starting up");
+
+        mProcessingServiceManager = ProcessingServiceManager.getInstance();
+        mSessionManager = getServices().getCaptureSessionManager();
+
+        // Keep CPU awake while allowing screen and keyboard to switch off.
+        PowerManager powerManager = (PowerManager) getSystemService(
+                Context.POWER_SERVICE);
+        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+        mWakeLock.acquire();
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(TAG, "Shutting down");
+
+        // Tell the manager that we're shutting down, so in case new tasks are
+        // enqueued, we a new service needs to be started.
+        mProcessingServiceManager.notifyStitchingFinished();
+
+        // TODO: Cancel session in progress...
+
+        // Unlock the power manager, i.e. let power management kick in if
+        // needed.
+        if (mWakeLock.isHeld()) {
+            mWakeLock.release();
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        asyncProcessAllTasksAndShutdown();
+
+        // We want this service to continue running until it is explicitly
+        // stopped, so return sticky.
+        return START_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        // We don't provide binding, so return null.
+        return null;
+    }
+
+    /**
+     * Starts a thread to process all tasks. When no more tasks are in the
+     * queue, it exits the thread and shuts down the service.
+     */
+    private void asyncProcessAllTasksAndShutdown() {
+        if (mProcessingThread != null) {
+            return;
+        }
+        mProcessingThread = new Thread() {
+            @Override
+            public void run() {
+                // Set the thread priority
+                android.os.Process.setThreadPriority(THREAD_PRIORITY);
+
+                ProcessingTask task;
+                while ((task = mProcessingServiceManager.popNextSession()) != null) {
+                    processAndNotify(task);
+                }
+                stopSelf();
+            }
+        };
+        mProcessingThread.start();
+    }
+
+    /**
+     * Processes a {@code ProcessingTask} and updates the notification bar.
+     */
+    void processAndNotify(ProcessingTask task) {
+        if (task == null) {
+            Log.e(TAG, "Reference to ProcessingTask is null");
+            return;
+        }
+        task.process(this, mSessionManager.createNewSession(task.getName()));
+    }
+
+    /**
+     * Returns the common camera services.
+     */
+    private CameraServices getServices() {
+        return (CameraApp) this.getApplication();
+    }
+}
diff --git a/src/com/android/camera/processing/ProcessingServiceManager.java b/src/com/android/camera/processing/ProcessingServiceManager.java
new file mode 100644
index 0000000..c4a0fa0
--- /dev/null
+++ b/src/com/android/camera/processing/ProcessingServiceManager.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.processing;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import java.util.LinkedList;
+import java.util.NoSuchElementException;
+
+/**
+ * Manages a queue of processing tasks as well as the processing service
+ * lifecycle.
+ * <p>
+ * Clients should only use this class and not the {@link ProcessingService}
+ * directly.
+ */
+public class ProcessingServiceManager {
+    private static final String TAG = "ProcessingServiceManager";
+
+    /** The singleton instance of this manager. */
+    private static ProcessingServiceManager sInstance;
+
+    /** The application context. */
+    private final Context mAppContext;
+
+    /** Queue of tasks to be processed. */
+    private final LinkedList<ProcessingTask> mQueue = new LinkedList<ProcessingTask>();
+
+    /** Whether a processing service is currently running. */
+    private volatile boolean mServiceRunning = false;
+
+    /**
+     * Initializes the singleton instance.
+     *
+     * @param context the application context.
+     */
+    public static void initSingleton(Context appContext) {
+        sInstance = new ProcessingServiceManager(appContext);
+    }
+
+    /**
+     * Note: Make sure to call {@link #initSingleton(Context)} first.
+     *
+     * @return the singleton instance of the processing service manager.
+     */
+    public static ProcessingServiceManager getInstance() {
+        if (sInstance == null) {
+            throw new IllegalStateException("initSingleton() not yet called.");
+        }
+        return sInstance;
+    }
+
+    private ProcessingServiceManager(Context context) {
+        mAppContext = context;
+    }
+
+    /**
+     * Enqueues a new task. If the service is not already running, it will be
+     * started.
+     *
+     * @param task The task to be enqueued.
+     */
+    public synchronized void enqueueTask(ProcessingTask task) {
+        mQueue.add(task);
+        Log.d(TAG, "Task added. Queue size now: " + mQueue.size());
+
+        if (!mServiceRunning) {
+            // Starts the service which will then work through the queue. Once
+            // the queue is empty (#popNextSession() returns null), the task
+            // will kill itself automatically and call #stitchingFinished().
+            mAppContext.startService(new Intent(mAppContext, ProcessingService.class));
+        }
+        mServiceRunning = true;
+    }
+
+    /**
+     * Remove the next task from the queue and return it.
+     *
+     * @return The next Task or <code>null</code>, of no more tasks are in the
+     *         queue.
+     */
+    public synchronized ProcessingTask popNextSession() {
+        try {
+            return mQueue.remove();
+        } catch (NoSuchElementException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Called by the processing service, notifying us that it has finished.
+     */
+    public synchronized void notifyStitchingFinished() {
+        this.mServiceRunning = false;
+    }
+}
diff --git a/src/com/android/camera/processing/ProcessingTask.java b/src/com/android/camera/processing/ProcessingTask.java
new file mode 100644
index 0000000..47cb4f2
--- /dev/null
+++ b/src/com/android/camera/processing/ProcessingTask.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.processing;
+
+import android.content.Context;
+
+import com.android.camera.session.CaptureSession;
+
+/**
+ * An interface for tasks to be processed by a {@code ProcessingService}.
+ */
+public interface ProcessingTask {
+    /**
+     * The result returned by a {@code ProcessingTask}.
+     */
+    public class ProcessingResult {
+        public final boolean mSuccess;
+        public final CaptureSession mSession;
+
+        /**
+         * @param success whether the processing was successful.
+         * @param session the capture session for the processed task.
+         */
+        public ProcessingResult(boolean success, CaptureSession session) {
+            mSuccess = success;
+            mSession = session;
+        }
+    }
+
+    /**
+     * Classes implementing this interface can be informed when a task is done
+     * processing.
+     */
+    public interface ProcessingTaskDoneListener {
+        /**
+         * Called when a task is done processing.
+         *
+         * @param result the processing result.
+         */
+        public void onDone(ProcessingResult result);
+    }
+
+    /**
+     * Processes the given task. This will be usually called by a service.
+     *
+     * @param context the caller {@code Context}
+     * @param session the {@code CaptureSession}
+     * @return the {@code ProcessResult} with the result of the processing
+     */
+    public ProcessingResult process(Context context, CaptureSession session);
+
+    /**
+     * @return the name of the task. It can be null to indicate that the task
+     *         has no name.
+     */
+    public String getName();
+
+    /** Sets a listener that is informed when this task is done processing. */
+    public void setDoneListener(ProcessingTaskDoneListener listener);
+}