Home | History | Annotate | Download | only in impl
      1 /*
      2  * Copyright (C) 2013 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 android.hardware.camera2.impl;
     18 
     19 import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE;
     20 
     21 import android.hardware.camera2.CameraAccessException;
     22 import android.hardware.camera2.CaptureRequest;
     23 import android.hardware.camera2.CaptureResult;
     24 import android.hardware.camera2.ICameraDeviceCallbacks;
     25 import android.hardware.camera2.ICameraDeviceUser;
     26 import android.hardware.camera2.utils.CameraBinderDecorator;
     27 import android.hardware.camera2.utils.CameraRuntimeException;
     28 import android.os.Handler;
     29 import android.os.IBinder;
     30 import android.os.Looper;
     31 import android.os.RemoteException;
     32 import android.util.Log;
     33 import android.util.SparseArray;
     34 import android.view.Surface;
     35 
     36 import java.util.ArrayList;
     37 import java.util.HashSet;
     38 import java.util.Iterator;
     39 import java.util.List;
     40 
     41 /**
     42  * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
     43  */
     44 public class CameraDevice implements android.hardware.camera2.CameraDevice {
     45 
     46     private final String TAG;
     47     private final boolean DEBUG;
     48 
     49     private static final int REQUEST_ID_NONE = -1;
     50 
     51     // TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
     52     private ICameraDeviceUser mRemoteDevice;
     53 
     54     private final Object mLock = new Object();
     55     private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
     56 
     57     private final StateListener mDeviceListener;
     58     private final Handler mDeviceHandler;
     59 
     60     private boolean mIdle = true;
     61 
     62     private final SparseArray<CaptureListenerHolder> mCaptureListenerMap =
     63             new SparseArray<CaptureListenerHolder>();
     64 
     65     private int mRepeatingRequestId = REQUEST_ID_NONE;
     66     private final ArrayList<Integer> mRepeatingRequestIdDeletedList = new ArrayList<Integer>();
     67     // Map stream IDs to Surfaces
     68     private final SparseArray<Surface> mConfiguredOutputs = new SparseArray<Surface>();
     69 
     70     private final String mCameraId;
     71 
     72     // Runnables for all state transitions, except error, which needs the
     73     // error code argument
     74 
     75     private final Runnable mCallOnOpened = new Runnable() {
     76         public void run() {
     77             if (!CameraDevice.this.isClosed()) {
     78                 mDeviceListener.onOpened(CameraDevice.this);
     79             }
     80         }
     81     };
     82 
     83     private final Runnable mCallOnUnconfigured = new Runnable() {
     84         public void run() {
     85             if (!CameraDevice.this.isClosed()) {
     86                 mDeviceListener.onUnconfigured(CameraDevice.this);
     87             }
     88         }
     89     };
     90 
     91     private final Runnable mCallOnActive = new Runnable() {
     92         public void run() {
     93             if (!CameraDevice.this.isClosed()) {
     94                 mDeviceListener.onActive(CameraDevice.this);
     95             }
     96         }
     97     };
     98 
     99     private final Runnable mCallOnBusy = new Runnable() {
    100         public void run() {
    101             if (!CameraDevice.this.isClosed()) {
    102                 mDeviceListener.onBusy(CameraDevice.this);
    103             }
    104         }
    105     };
    106 
    107     private final Runnable mCallOnClosed = new Runnable() {
    108         public void run() {
    109             if (!CameraDevice.this.isClosed()) {
    110                 mDeviceListener.onClosed(CameraDevice.this);
    111             }
    112         }
    113     };
    114 
    115     private final Runnable mCallOnIdle = new Runnable() {
    116         public void run() {
    117             if (!CameraDevice.this.isClosed()) {
    118                 mDeviceListener.onIdle(CameraDevice.this);
    119             }
    120         }
    121     };
    122 
    123     private final Runnable mCallOnDisconnected = new Runnable() {
    124         public void run() {
    125             if (!CameraDevice.this.isClosed()) {
    126                 mDeviceListener.onDisconnected(CameraDevice.this);
    127             }
    128         }
    129     };
    130 
    131     public CameraDevice(String cameraId, StateListener listener, Handler handler) {
    132         if (cameraId == null || listener == null || handler == null) {
    133             throw new IllegalArgumentException("Null argument given");
    134         }
    135         mCameraId = cameraId;
    136         mDeviceListener = listener;
    137         mDeviceHandler = handler;
    138         TAG = String.format("CameraDevice-%s-JV", mCameraId);
    139         DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    140     }
    141 
    142     public CameraDeviceCallbacks getCallbacks() {
    143         return mCallbacks;
    144     }
    145 
    146     public void setRemoteDevice(ICameraDeviceUser remoteDevice) {
    147         // TODO: Move from decorator to direct binder-mediated exceptions
    148         synchronized(mLock) {
    149             mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice);
    150 
    151             mDeviceHandler.post(mCallOnOpened);
    152             mDeviceHandler.post(mCallOnUnconfigured);
    153         }
    154     }
    155 
    156     @Override
    157     public String getId() {
    158         return mCameraId;
    159     }
    160 
    161     @Override
    162     public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
    163         // Treat a null input the same an empty list
    164         if (outputs == null) {
    165             outputs = new ArrayList<Surface>();
    166         }
    167         synchronized (mLock) {
    168             checkIfCameraClosed();
    169 
    170             HashSet<Surface> addSet = new HashSet<Surface>(outputs);    // Streams to create
    171             List<Integer> deleteList = new ArrayList<Integer>();        // Streams to delete
    172 
    173             // Determine which streams need to be created, which to be deleted
    174             for (int i = 0; i < mConfiguredOutputs.size(); ++i) {
    175                 int streamId = mConfiguredOutputs.keyAt(i);
    176                 Surface s = mConfiguredOutputs.valueAt(i);
    177 
    178                 if (!outputs.contains(s)) {
    179                     deleteList.add(streamId);
    180                 } else {
    181                     addSet.remove(s);  // Don't create a stream previously created
    182                 }
    183             }
    184 
    185             mDeviceHandler.post(mCallOnBusy);
    186             stopRepeating();
    187 
    188             try {
    189                 waitUntilIdle();
    190 
    191                 // TODO: mRemoteDevice.beginConfigure
    192                 // Delete all streams first (to free up HW resources)
    193                 for (Integer streamId : deleteList) {
    194                     mRemoteDevice.deleteStream(streamId);
    195                     mConfiguredOutputs.delete(streamId);
    196                 }
    197 
    198                 // Add all new streams
    199                 for (Surface s : addSet) {
    200                     // TODO: remove width,height,format since we are ignoring
    201                     // it.
    202                     int streamId = mRemoteDevice.createStream(0, 0, 0, s);
    203                     mConfiguredOutputs.put(streamId, s);
    204                 }
    205 
    206                 // TODO: mRemoteDevice.endConfigure
    207             } catch (CameraRuntimeException e) {
    208                 if (e.getReason() == CAMERA_IN_USE) {
    209                     throw new IllegalStateException("The camera is currently busy." +
    210                             " You must wait until the previous operation completes.");
    211                 }
    212 
    213                 throw e.asChecked();
    214             } catch (RemoteException e) {
    215                 // impossible
    216                 return;
    217             }
    218 
    219             if (outputs.size() > 0) {
    220                 mDeviceHandler.post(mCallOnIdle);
    221             } else {
    222                 mDeviceHandler.post(mCallOnUnconfigured);
    223             }
    224         }
    225     }
    226 
    227     @Override
    228     public CaptureRequest.Builder createCaptureRequest(int templateType)
    229             throws CameraAccessException {
    230         synchronized (mLock) {
    231             checkIfCameraClosed();
    232 
    233             CameraMetadataNative templatedRequest = new CameraMetadataNative();
    234 
    235             try {
    236                 mRemoteDevice.createDefaultRequest(templateType, /* out */templatedRequest);
    237             } catch (CameraRuntimeException e) {
    238                 throw e.asChecked();
    239             } catch (RemoteException e) {
    240                 // impossible
    241                 return null;
    242             }
    243 
    244             CaptureRequest.Builder builder =
    245                     new CaptureRequest.Builder(templatedRequest);
    246 
    247             return builder;
    248         }
    249     }
    250 
    251     @Override
    252     public int capture(CaptureRequest request, CaptureListener listener, Handler handler)
    253             throws CameraAccessException {
    254         return submitCaptureRequest(request, listener, handler, /*streaming*/false);
    255     }
    256 
    257     @Override
    258     public int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
    259             Handler handler) throws CameraAccessException {
    260         if (requests.isEmpty()) {
    261             Log.w(TAG, "Capture burst request list is empty, do nothing!");
    262             return -1;
    263         }
    264         // TODO
    265         throw new UnsupportedOperationException("Burst capture implemented yet");
    266 
    267     }
    268 
    269     private int submitCaptureRequest(CaptureRequest request, CaptureListener listener,
    270             Handler handler, boolean repeating) throws CameraAccessException {
    271 
    272         // Need a valid handler, or current thread needs to have a looper, if
    273         // listener is valid
    274         if (listener != null) {
    275             handler = checkHandler(handler);
    276         }
    277 
    278         synchronized (mLock) {
    279             checkIfCameraClosed();
    280             int requestId;
    281 
    282             if (repeating) {
    283                 stopRepeating();
    284             }
    285 
    286             try {
    287                 requestId = mRemoteDevice.submitRequest(request, repeating);
    288             } catch (CameraRuntimeException e) {
    289                 throw e.asChecked();
    290             } catch (RemoteException e) {
    291                 // impossible
    292                 return -1;
    293             }
    294             if (listener != null) {
    295                 mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener, request,
    296                         handler, repeating));
    297             }
    298 
    299             if (repeating) {
    300                 mRepeatingRequestId = requestId;
    301             }
    302 
    303             if (mIdle) {
    304                 mDeviceHandler.post(mCallOnActive);
    305             }
    306             mIdle = false;
    307 
    308             return requestId;
    309         }
    310     }
    311 
    312     @Override
    313     public int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
    314             Handler handler) throws CameraAccessException {
    315         return submitCaptureRequest(request, listener, handler, /*streaming*/true);
    316     }
    317 
    318     @Override
    319     public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
    320             Handler handler) throws CameraAccessException {
    321         if (requests.isEmpty()) {
    322             Log.w(TAG, "Set Repeating burst request list is empty, do nothing!");
    323             return -1;
    324         }
    325         // TODO
    326         throw new UnsupportedOperationException("Burst capture implemented yet");
    327     }
    328 
    329     @Override
    330     public void stopRepeating() throws CameraAccessException {
    331 
    332         synchronized (mLock) {
    333             checkIfCameraClosed();
    334             if (mRepeatingRequestId != REQUEST_ID_NONE) {
    335 
    336                 int requestId = mRepeatingRequestId;
    337                 mRepeatingRequestId = REQUEST_ID_NONE;
    338 
    339                 // Queue for deletion after in-flight requests finish
    340                 mRepeatingRequestIdDeletedList.add(requestId);
    341 
    342                 try {
    343                     mRemoteDevice.cancelRequest(requestId);
    344                 } catch (CameraRuntimeException e) {
    345                     throw e.asChecked();
    346                 } catch (RemoteException e) {
    347                     // impossible
    348                     return;
    349                 }
    350             }
    351         }
    352     }
    353 
    354     @Override
    355     public void waitUntilIdle() throws CameraAccessException {
    356 
    357         synchronized (mLock) {
    358             checkIfCameraClosed();
    359             if (mRepeatingRequestId != REQUEST_ID_NONE) {
    360                 throw new IllegalStateException("Active repeating request ongoing");
    361             }
    362 
    363             try {
    364                 mRemoteDevice.waitUntilIdle();
    365             } catch (CameraRuntimeException e) {
    366                 throw e.asChecked();
    367             } catch (RemoteException e) {
    368                 // impossible
    369                 return;
    370             }
    371 
    372             mRepeatingRequestId = REQUEST_ID_NONE;
    373             mRepeatingRequestIdDeletedList.clear();
    374             mCaptureListenerMap.clear();
    375         }
    376     }
    377 
    378     @Override
    379     public void flush() throws CameraAccessException {
    380         synchronized (mLock) {
    381             checkIfCameraClosed();
    382 
    383             mDeviceHandler.post(mCallOnBusy);
    384             try {
    385                 mRemoteDevice.flush();
    386             } catch (CameraRuntimeException e) {
    387                 throw e.asChecked();
    388             } catch (RemoteException e) {
    389                 // impossible
    390                 return;
    391             }
    392         }
    393     }
    394 
    395     @Override
    396     public void close() {
    397         synchronized (mLock) {
    398 
    399             try {
    400                 if (mRemoteDevice != null) {
    401                     mRemoteDevice.disconnect();
    402                 }
    403             } catch (CameraRuntimeException e) {
    404                 Log.e(TAG, "Exception while closing: ", e.asChecked());
    405             } catch (RemoteException e) {
    406                 // impossible
    407             }
    408 
    409             if (mRemoteDevice != null) {
    410                 mDeviceHandler.post(mCallOnClosed);
    411             }
    412 
    413             mRemoteDevice = null;
    414         }
    415     }
    416 
    417     @Override
    418     protected void finalize() throws Throwable {
    419         try {
    420             close();
    421         }
    422         finally {
    423             super.finalize();
    424         }
    425     }
    426 
    427     static class CaptureListenerHolder {
    428 
    429         private final boolean mRepeating;
    430         private final CaptureListener mListener;
    431         private final CaptureRequest mRequest;
    432         private final Handler mHandler;
    433 
    434         CaptureListenerHolder(CaptureListener listener, CaptureRequest request, Handler handler,
    435                 boolean repeating) {
    436             if (listener == null || handler == null) {
    437                 throw new UnsupportedOperationException(
    438                     "Must have a valid handler and a valid listener");
    439             }
    440             mRepeating = repeating;
    441             mHandler = handler;
    442             mRequest = request;
    443             mListener = listener;
    444         }
    445 
    446         public boolean isRepeating() {
    447             return mRepeating;
    448         }
    449 
    450         public CaptureListener getListener() {
    451             return mListener;
    452         }
    453 
    454         public CaptureRequest getRequest() {
    455             return mRequest;
    456         }
    457 
    458         public Handler getHandler() {
    459             return mHandler;
    460         }
    461 
    462     }
    463 
    464     public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
    465 
    466         //
    467         // Constants below need to be kept up-to-date with
    468         // frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
    469         //
    470 
    471         //
    472         // Error codes for onCameraError
    473         //
    474 
    475         /**
    476          * Camera has been disconnected
    477          */
    478         static final int ERROR_CAMERA_DISCONNECTED = 0;
    479 
    480         /**
    481          * Camera has encountered a device-level error
    482          * Matches CameraDevice.StateListener#ERROR_CAMERA_DEVICE
    483          */
    484         static final int ERROR_CAMERA_DEVICE = 1;
    485 
    486         /**
    487          * Camera has encountered a service-level error
    488          * Matches CameraDevice.StateListener#ERROR_CAMERA_SERVICE
    489          */
    490         static final int ERROR_CAMERA_SERVICE = 2;
    491 
    492         @Override
    493         public IBinder asBinder() {
    494             return this;
    495         }
    496 
    497         @Override
    498         public void onCameraError(final int errorCode) {
    499             Runnable r = null;
    500             if (isClosed()) return;
    501 
    502             synchronized(mLock) {
    503                 switch (errorCode) {
    504                     case ERROR_CAMERA_DISCONNECTED:
    505                         r = mCallOnDisconnected;
    506                         break;
    507                     default:
    508                         Log.e(TAG, "Unknown error from camera device: " + errorCode);
    509                         // no break
    510                     case ERROR_CAMERA_DEVICE:
    511                     case ERROR_CAMERA_SERVICE:
    512                         r = new Runnable() {
    513                             public void run() {
    514                                 if (!CameraDevice.this.isClosed()) {
    515                                     mDeviceListener.onError(CameraDevice.this, errorCode);
    516                                 }
    517                             }
    518                         };
    519                         break;
    520                 }
    521                 CameraDevice.this.mDeviceHandler.post(r);
    522             }
    523         }
    524 
    525         @Override
    526         public void onCameraIdle() {
    527             if (isClosed()) return;
    528 
    529             if (DEBUG) {
    530                 Log.d(TAG, "Camera now idle");
    531             }
    532             synchronized (mLock) {
    533                 if (!CameraDevice.this.mIdle) {
    534                     CameraDevice.this.mDeviceHandler.post(mCallOnIdle);
    535                 }
    536                 CameraDevice.this.mIdle = true;
    537             }
    538         }
    539 
    540         @Override
    541         public void onCaptureStarted(int requestId, final long timestamp) {
    542             if (DEBUG) {
    543                 Log.d(TAG, "Capture started for id " + requestId);
    544             }
    545             final CaptureListenerHolder holder;
    546 
    547             // Get the listener for this frame ID, if there is one
    548             synchronized (mLock) {
    549                 holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
    550             }
    551 
    552             if (holder == null) {
    553                 return;
    554             }
    555 
    556             if (isClosed()) return;
    557 
    558             // Dispatch capture start notice
    559             holder.getHandler().post(
    560                 new Runnable() {
    561                     public void run() {
    562                         if (!CameraDevice.this.isClosed()) {
    563                             holder.getListener().onCaptureStarted(
    564                                 CameraDevice.this,
    565                                 holder.getRequest(),
    566                                 timestamp);
    567                         }
    568                     }
    569                 });
    570         }
    571 
    572         @Override
    573         public void onResultReceived(int requestId, CameraMetadataNative result)
    574                 throws RemoteException {
    575             if (DEBUG) {
    576                 Log.d(TAG, "Received result for id " + requestId);
    577             }
    578             final CaptureListenerHolder holder;
    579 
    580             Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
    581             boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
    582 
    583             synchronized (mLock) {
    584                 // TODO: move this whole map into this class to make it more testable,
    585                 //        exposing the methods necessary like subscribeToRequest, unsubscribe..
    586                 // TODO: make class static class
    587 
    588                 holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
    589 
    590                 // Clean up listener once we no longer expect to see it.
    591                 if (holder != null && !holder.isRepeating() && !quirkIsPartialResult) {
    592                     CameraDevice.this.mCaptureListenerMap.remove(requestId);
    593                 }
    594 
    595                 // TODO: add 'capture sequence completed' callback to the
    596                 // service, and clean up repeating requests there instead.
    597 
    598                 // If we received a result for a repeating request and have
    599                 // prior repeating requests queued for deletion, remove those
    600                 // requests from mCaptureListenerMap.
    601                 if (holder != null && holder.isRepeating() && !quirkIsPartialResult
    602                         && mRepeatingRequestIdDeletedList.size() > 0) {
    603                     Iterator<Integer> iter = mRepeatingRequestIdDeletedList.iterator();
    604                     while (iter.hasNext()) {
    605                         int deletedRequestId = iter.next();
    606                         if (deletedRequestId < requestId) {
    607                             CameraDevice.this.mCaptureListenerMap.remove(deletedRequestId);
    608                             iter.remove();
    609                         }
    610                     }
    611                 }
    612 
    613             }
    614 
    615             // Check if we have a listener for this
    616             if (holder == null) {
    617                 return;
    618             }
    619 
    620             if (isClosed()) return;
    621 
    622             final CaptureRequest request = holder.getRequest();
    623             final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId);
    624 
    625             Runnable resultDispatch = null;
    626 
    627             // Either send a partial result or the final capture completed result
    628             if (quirkIsPartialResult) {
    629                 // Partial result
    630                 resultDispatch = new Runnable() {
    631                     @Override
    632                     public void run() {
    633                         if (!CameraDevice.this.isClosed()){
    634                             holder.getListener().onCapturePartial(
    635                                 CameraDevice.this,
    636                                 request,
    637                                 resultAsCapture);
    638                         }
    639                     }
    640                 };
    641             } else {
    642                 // Final capture result
    643                 resultDispatch = new Runnable() {
    644                     @Override
    645                     public void run() {
    646                         if (!CameraDevice.this.isClosed()){
    647                             holder.getListener().onCaptureCompleted(
    648                                 CameraDevice.this,
    649                                 request,
    650                                 resultAsCapture);
    651                         }
    652                     }
    653                 };
    654             }
    655 
    656             holder.getHandler().post(resultDispatch);
    657         }
    658 
    659     }
    660 
    661     /**
    662      * Default handler management. If handler is null, get the current thread's
    663      * Looper to create a Handler with. If no looper exists, throw exception.
    664      */
    665     private Handler checkHandler(Handler handler) {
    666         if (handler == null) {
    667             Looper looper = Looper.myLooper();
    668             if (looper == null) {
    669                 throw new IllegalArgumentException(
    670                     "No handler given, and current thread has no looper!");
    671             }
    672             handler = new Handler(looper);
    673         }
    674         return handler;
    675     }
    676 
    677     private void checkIfCameraClosed() {
    678         if (mRemoteDevice == null) {
    679             throw new IllegalStateException("CameraDevice was already closed");
    680         }
    681     }
    682 
    683     private boolean isClosed() {
    684         synchronized(mLock) {
    685             return (mRemoteDevice == null);
    686         }
    687     }
    688 }
    689