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.CameraServices;
     33 import com.android.camera.app.CameraServicesImpl;
     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.camera.util.AndroidServices;
     39 import com.android.camera2.R;
     40 
     41 import java.util.concurrent.locks.Lock;
     42 import java.util.concurrent.locks.ReentrantLock;
     43 
     44 /**
     45  * A service that processes a {@code ProcessingTask}. The service uses a fifo
     46  * queue so that only one {@code ProcessingTask} is processed at a time.
     47  * <p>
     48  * The service is meant to be called via {@code ProcessingService.addTask},
     49  * which takes care of starting the service and enqueueing the
     50  * {@code ProcessingTask} task:
     51  *
     52  * <pre>
     53  * {@code
     54  * ProcessingTask task = new MyProcessingTask(...);
     55  * ProcessingService.addTask(task);
     56  * }
     57  * </pre>
     58  */
     59 public class ProcessingService extends Service implements ProgressListener {
     60     /**
     61      * Class used to receive broadcast and control the service accordingly.
     62      */
     63     public class ServiceController extends BroadcastReceiver {
     64         @Override
     65         public void onReceive(Context context, Intent intent) {
     66             if (intent.getAction() == ACTION_PAUSE_PROCESSING_SERVICE) {
     67                 ProcessingService.this.pause();
     68             } else if (intent.getAction() == ACTION_RESUME_PROCESSING_SERVICE) {
     69                 ProcessingService.this.resume();
     70             }
     71         }
     72     }
     73 
     74     private static final Log.Tag TAG = new Log.Tag("ProcessingService");
     75     private static final int THREAD_PRIORITY = Process.THREAD_PRIORITY_BACKGROUND;
     76     private static final int CAMERA_NOTIFICATION_ID = 2;
     77     private Notification.Builder mNotificationBuilder;
     78     private NotificationManager mNotificationManager;
     79 
     80     /** Sending this broadcast intent will cause the processing to pause. */
     81     public static final String ACTION_PAUSE_PROCESSING_SERVICE =
     82             "com.android.camera.processing.PAUSE";
     83     /**
     84      * Sending this broadcast intent will cause the processing to resume after
     85      * it has been paused.
     86      */
     87     public static final String ACTION_RESUME_PROCESSING_SERVICE =
     88             "com.android.camera.processing.RESUME";
     89 
     90     private WakeLock mWakeLock;
     91     private final ServiceController mServiceController = new ServiceController();
     92 
     93     /** Manages the capture session. */
     94     private CaptureSessionManager mSessionManager;
     95 
     96     private ProcessingServiceManager mProcessingServiceManager;
     97     private Thread mProcessingThread;
     98     private volatile boolean mPaused = false;
     99     private ProcessingTask mCurrentTask;
    100     private final Lock mSuspendStatusLock = new ReentrantLock();
    101 
    102     @Override
    103     public void onCreate() {
    104         mProcessingServiceManager = ProcessingServiceManager.instance();
    105         mSessionManager = getServices().getCaptureSessionManager();
    106 
    107         // Keep CPU awake while allowing screen and keyboard to switch off.
    108         PowerManager powerManager = AndroidServices.instance().providePowerManager();
    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 = AndroidServices.instance().provideNotificationManager();
    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 
    224         // TODO: Get rid of this null check. There should not be a task without
    225         // a session.
    226         if (session == null) {
    227             // TODO: Timestamp is not required right now, refactor this to make it clearer.
    228             session = mSessionManager.createNewSession(task.getName(), 0, task.getLocation());
    229         }
    230         resetNotification();
    231 
    232         // Adding the listener also causes it to get called for the session's
    233         // current status message and percent completed.
    234         session.addProgressListener(this);
    235 
    236         System.gc();
    237         Log.d(TAG, "Processing start");
    238         task.process(this, getServices(), session);
    239         Log.d(TAG, "Processing done");
    240     }
    241 
    242     private void resetNotification() {
    243         mNotificationBuilder.setContentText("").setProgress(100, 0, false);
    244         postNotification();
    245     }
    246 
    247     /**
    248      * Returns the common camera services.
    249      */
    250     private CameraServices getServices() {
    251         return CameraServicesImpl.instance();
    252     }
    253 
    254     private void postNotification() {
    255         mNotificationManager.notify(CAMERA_NOTIFICATION_ID, mNotificationBuilder.build());
    256     }
    257 
    258     /**
    259      * Creates a notification to indicate that a computation is in progress.
    260      */
    261     private Notification.Builder createInProgressNotificationBuilder() {
    262         return new Notification.Builder(this)
    263                 .setSmallIcon(R.drawable.ic_notification)
    264                 .setWhen(System.currentTimeMillis())
    265                 .setOngoing(true)
    266                 .setContentTitle(this.getText(R.string.app_name));
    267     }
    268 
    269     @Override
    270     public void onProgressChanged(int progress) {
    271         mNotificationBuilder.setProgress(100, progress, false);
    272         postNotification();
    273     }
    274 
    275     @Override
    276     public void onStatusMessageChanged(int messageId) {
    277         mNotificationBuilder.setContentText(messageId > 0 ? getString(messageId) : "");
    278         postNotification();
    279     }
    280 }
    281