Home | History | Annotate | Download | only in processing
      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