Home | History | Annotate | Download | only in camera2
      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;
     18 
     19 import android.content.Context;
     20 import android.hardware.ICameraService;
     21 import android.hardware.ICameraServiceListener;
     22 import android.hardware.IProCameraUser;
     23 import android.hardware.camera2.impl.CameraMetadataNative;
     24 import android.hardware.camera2.utils.CameraBinderDecorator;
     25 import android.hardware.camera2.utils.CameraRuntimeException;
     26 import android.hardware.camera2.utils.BinderHolder;
     27 import android.os.IBinder;
     28 import android.os.Handler;
     29 import android.os.Looper;
     30 import android.os.RemoteException;
     31 import android.os.ServiceManager;
     32 import android.util.Log;
     33 import android.util.ArrayMap;
     34 
     35 import java.util.ArrayList;
     36 
     37 /**
     38  * <p>An interface for iterating, listing, and connecting to
     39  * {@link CameraDevice CameraDevices}.</p>
     40  *
     41  * <p>You can get an instance of this class by calling
     42  * {@link android.content.Context#getSystemService(String) Context.getSystemService()}.</p>
     43  *
     44  * <pre>CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);</pre>
     45  *
     46  * <p>For more details about communicating with camera devices, read the Camera
     47  * developer guide or the {@link android.hardware.camera2 camera2}
     48  * package documentation.</p>
     49  */
     50 public final class CameraManager {
     51 
     52     /**
     53      * This should match the ICameraService definition
     54      */
     55     private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
     56     private static final int USE_CALLING_UID = -1;
     57 
     58     private final ICameraService mCameraService;
     59     private ArrayList<String> mDeviceIdList;
     60 
     61     private final ArrayMap<AvailabilityListener, Handler> mListenerMap =
     62             new ArrayMap<AvailabilityListener, Handler>();
     63 
     64     private final Context mContext;
     65     private final Object mLock = new Object();
     66 
     67     /**
     68      * @hide
     69      */
     70     public CameraManager(Context context) {
     71         mContext = context;
     72 
     73         IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME);
     74         ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder);
     75 
     76         /**
     77          * Wrap the camera service in a decorator which automatically translates return codes
     78          * into exceptions, and RemoteExceptions into other exceptions.
     79          */
     80         mCameraService = CameraBinderDecorator.newInstance(cameraServiceRaw);
     81 
     82         try {
     83             mCameraService.addListener(new CameraServiceListener());
     84         } catch(CameraRuntimeException e) {
     85             throw new IllegalStateException("Failed to register a camera service listener",
     86                     e.asChecked());
     87         } catch (RemoteException e) {
     88             // impossible
     89         }
     90     }
     91 
     92     /**
     93      * Return the list of currently connected camera devices by
     94      * identifier.
     95      *
     96      * <p>Non-removable cameras use integers starting at 0 for their
     97      * identifiers, while removable cameras have a unique identifier for each
     98      * individual device, even if they are the same model.</p>
     99      *
    100      * @return The list of currently connected camera devices.
    101      */
    102     public String[] getCameraIdList() throws CameraAccessException {
    103         synchronized (mLock) {
    104             try {
    105                 return getOrCreateDeviceIdListLocked().toArray(new String[0]);
    106             } catch(CameraAccessException e) {
    107                 // this should almost never happen, except if mediaserver crashes
    108                 throw new IllegalStateException(
    109                         "Failed to query camera service for device ID list", e);
    110             }
    111         }
    112     }
    113 
    114     /**
    115      * Register a listener to be notified about camera device availability.
    116      *
    117      * <p>Registering the same listener again will replace the handler with the
    118      * new one provided.</p>
    119      *
    120      * @param listener The new listener to send camera availability notices to
    121      * @param handler The handler on which the listener should be invoked, or
    122      * {@code null} to use the current thread's {@link android.os.Looper looper}.
    123      */
    124     public void addAvailabilityListener(AvailabilityListener listener, Handler handler) {
    125         if (handler == null) {
    126             Looper looper = Looper.myLooper();
    127             if (looper == null) {
    128                 throw new IllegalArgumentException(
    129                         "No handler given, and current thread has no looper!");
    130             }
    131             handler = new Handler(looper);
    132         }
    133 
    134         synchronized (mLock) {
    135             mListenerMap.put(listener, handler);
    136         }
    137     }
    138 
    139     /**
    140      * Remove a previously-added listener; the listener will no longer receive
    141      * connection and disconnection callbacks.
    142      *
    143      * <p>Removing a listener that isn't registered has no effect.</p>
    144      *
    145      * @param listener The listener to remove from the notification list
    146      */
    147     public void removeAvailabilityListener(AvailabilityListener listener) {
    148         synchronized (mLock) {
    149             mListenerMap.remove(listener);
    150         }
    151     }
    152 
    153     /**
    154      * <p>Query the capabilities of a camera device. These capabilities are
    155      * immutable for a given camera.</p>
    156      *
    157      * @param cameraId The id of the camera device to query
    158      * @return The properties of the given camera
    159      *
    160      * @throws IllegalArgumentException if the cameraId does not match any
    161      * currently connected camera device.
    162      * @throws CameraAccessException if the camera is disabled by device policy.
    163      * @throws SecurityException if the application does not have permission to
    164      * access the camera
    165      *
    166      * @see #getCameraIdList
    167      * @see android.app.admin.DevicePolicyManager#setCameraDisabled
    168      */
    169     public CameraCharacteristics getCameraCharacteristics(String cameraId)
    170             throws CameraAccessException {
    171 
    172         synchronized (mLock) {
    173             if (!getOrCreateDeviceIdListLocked().contains(cameraId)) {
    174                 throw new IllegalArgumentException(String.format("Camera id %s does not match any" +
    175                         " currently connected camera device", cameraId));
    176             }
    177         }
    178 
    179         CameraMetadataNative info = new CameraMetadataNative();
    180         try {
    181             mCameraService.getCameraCharacteristics(Integer.valueOf(cameraId), info);
    182         } catch(CameraRuntimeException e) {
    183             throw e.asChecked();
    184         } catch(RemoteException e) {
    185             // impossible
    186             return null;
    187         }
    188 
    189         return new CameraCharacteristics(info);
    190     }
    191 
    192     /**
    193      * Open a connection to a camera with the given ID. Use
    194      * {@link #getCameraIdList} to get the list of available camera
    195      * devices. Note that even if an id is listed, open may fail if the device
    196      * is disconnected between the calls to {@link #getCameraIdList} and
    197      * {@link #openCamera}.
    198      *
    199      * @param cameraId The unique identifier of the camera device to open
    200      * @param listener The listener for the camera. Must not be null.
    201      * @param handler  The handler to call the listener on. Must not be null.
    202      *
    203      * @throws CameraAccessException if the camera is disabled by device policy,
    204      * or too many camera devices are already open, or the cameraId does not match
    205      * any currently available camera device.
    206      *
    207      * @throws SecurityException if the application does not have permission to
    208      * access the camera
    209      * @throws IllegalArgumentException if listener or handler is null.
    210      *
    211      * @see #getCameraIdList
    212      * @see android.app.admin.DevicePolicyManager#setCameraDisabled
    213      */
    214     private void openCameraDeviceUserAsync(String cameraId,
    215             CameraDevice.StateListener listener, Handler handler)
    216             throws CameraAccessException {
    217         try {
    218 
    219             synchronized (mLock) {
    220 
    221                 ICameraDeviceUser cameraUser;
    222 
    223                 android.hardware.camera2.impl.CameraDevice device =
    224                         new android.hardware.camera2.impl.CameraDevice(
    225                                 cameraId,
    226                                 listener,
    227                                 handler);
    228 
    229                 BinderHolder holder = new BinderHolder();
    230                 mCameraService.connectDevice(device.getCallbacks(),
    231                         Integer.parseInt(cameraId),
    232                         mContext.getPackageName(), USE_CALLING_UID, holder);
    233                 cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
    234 
    235                 // TODO: factor out listener to be non-nested, then move setter to constructor
    236                 // For now, calling setRemoteDevice will fire initial
    237                 // onOpened/onUnconfigured callbacks.
    238                 device.setRemoteDevice(cameraUser);
    239             }
    240 
    241         } catch (NumberFormatException e) {
    242             throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: "
    243                     + cameraId);
    244         } catch (CameraRuntimeException e) {
    245             throw e.asChecked();
    246         } catch (RemoteException e) {
    247             // impossible
    248         }
    249     }
    250 
    251     /**
    252      * Open a connection to a camera with the given ID.
    253      *
    254      * <p>Use {@link #getCameraIdList} to get the list of available camera
    255      * devices. Note that even if an id is listed, open may fail if the device
    256      * is disconnected between the calls to {@link #getCameraIdList} and
    257      * {@link #openCamera}.</p>
    258      *
    259      * <p>If the camera successfully opens after this function call returns,
    260      * {@link CameraDevice.StateListener#onOpened} will be invoked with the
    261      * newly opened {@link CameraDevice} in the unconfigured state.</p>
    262      *
    263      * <p>If the camera becomes disconnected during initialization
    264      * after this function call returns,
    265      * {@link CameraDevice.StateListener#onDisconnected} with a
    266      * {@link CameraDevice} in the disconnected state (and
    267      * {@link CameraDevice.StateListener#onOpened} will be skipped).</p>
    268      *
    269      * <p>If the camera fails to initialize after this function call returns,
    270      * {@link CameraDevice.StateListener#onError} will be invoked with a
    271      * {@link CameraDevice} in the error state (and
    272      * {@link CameraDevice.StateListener#onOpened} will be skipped).</p>
    273      *
    274      * @param cameraId
    275      *             The unique identifier of the camera device to open
    276      * @param listener
    277      *             The listener which is invoked once the camera is opened
    278      * @param handler
    279      *             The handler on which the listener should be invoked, or
    280      *             {@code null} to use the current thread's {@link android.os.Looper looper}.
    281      *
    282      * @throws CameraAccessException if the camera is disabled by device policy,
    283      * or the camera has become or was disconnected.
    284      *
    285      * @throws IllegalArgumentException if cameraId or the listener was null,
    286      * or the cameraId does not match any currently or previously available
    287      * camera device.
    288      *
    289      * @throws SecurityException if the application does not have permission to
    290      * access the camera
    291      *
    292      * @see #getCameraIdList
    293      * @see android.app.admin.DevicePolicyManager#setCameraDisabled
    294      */
    295     public void openCamera(String cameraId, final CameraDevice.StateListener listener,
    296             Handler handler)
    297             throws CameraAccessException {
    298 
    299         if (cameraId == null) {
    300             throw new IllegalArgumentException("cameraId was null");
    301         } else if (listener == null) {
    302             throw new IllegalArgumentException("listener was null");
    303         } else if (handler == null) {
    304             if (Looper.myLooper() != null) {
    305                 handler = new Handler();
    306             } else {
    307                 throw new IllegalArgumentException(
    308                         "Looper doesn't exist in the calling thread");
    309             }
    310         }
    311 
    312         openCameraDeviceUserAsync(cameraId, listener, handler);
    313     }
    314 
    315     /**
    316      * Interface for listening to camera devices becoming available or
    317      * unavailable.
    318      *
    319      * <p>Cameras become available when they are no longer in use, or when a new
    320      * removable camera is connected. They become unavailable when some
    321      * application or service starts using a camera, or when a removable camera
    322      * is disconnected.</p>
    323      *
    324      * @see addAvailabilityListener
    325      */
    326     public static abstract class AvailabilityListener {
    327 
    328         /**
    329          * A new camera has become available to use.
    330          *
    331          * <p>The default implementation of this method does nothing.</p>
    332          *
    333          * @param cameraId The unique identifier of the new camera.
    334          */
    335         public void onCameraAvailable(String cameraId) {
    336             // default empty implementation
    337         }
    338 
    339         /**
    340          * A previously-available camera has become unavailable for use.
    341          *
    342          * <p>If an application had an active CameraDevice instance for the
    343          * now-disconnected camera, that application will receive a
    344          * {@link CameraDevice.StateListener#onDisconnected disconnection error}.</p>
    345          *
    346          * <p>The default implementation of this method does nothing.</p>
    347          *
    348          * @param cameraId The unique identifier of the disconnected camera.
    349          */
    350         public void onCameraUnavailable(String cameraId) {
    351             // default empty implementation
    352         }
    353     }
    354 
    355     private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException {
    356         if (mDeviceIdList == null) {
    357             int numCameras = 0;
    358 
    359             try {
    360                 numCameras = mCameraService.getNumberOfCameras();
    361             } catch(CameraRuntimeException e) {
    362                 throw e.asChecked();
    363             } catch (RemoteException e) {
    364                 // impossible
    365                 return null;
    366             }
    367 
    368             mDeviceIdList = new ArrayList<String>();
    369             CameraMetadataNative info = new CameraMetadataNative();
    370             for (int i = 0; i < numCameras; ++i) {
    371                 // Non-removable cameras use integers starting at 0 for their
    372                 // identifiers
    373                 boolean isDeviceSupported = false;
    374                 try {
    375                     mCameraService.getCameraCharacteristics(i, info);
    376                     if (!info.isEmpty()) {
    377                         isDeviceSupported = true;
    378                     } else {
    379                         throw new AssertionError("Expected to get non-empty characteristics");
    380                     }
    381                 } catch(IllegalArgumentException  e) {
    382                     // Got a BAD_VALUE from service, meaning that this
    383                     // device is not supported.
    384                 } catch(CameraRuntimeException e) {
    385                     throw e.asChecked();
    386                 } catch(RemoteException e) {
    387                     // impossible
    388                 }
    389 
    390                 if (isDeviceSupported) {
    391                     mDeviceIdList.add(String.valueOf(i));
    392                 }
    393             }
    394 
    395         }
    396         return mDeviceIdList;
    397     }
    398 
    399     // TODO: this class needs unit tests
    400     // TODO: extract class into top level
    401     private class CameraServiceListener extends ICameraServiceListener.Stub {
    402 
    403         // Keep up-to-date with ICameraServiceListener.h
    404 
    405         // Device physically unplugged
    406         public static final int STATUS_NOT_PRESENT = 0;
    407         // Device physically has been plugged in
    408         // and the camera can be used exclusively
    409         public static final int STATUS_PRESENT = 1;
    410         // Device physically has been plugged in
    411         // but it will not be connect-able until enumeration is complete
    412         public static final int STATUS_ENUMERATING = 2;
    413         // Camera is in use by another app and cannot be used exclusively
    414         public static final int STATUS_NOT_AVAILABLE = 0x80000000;
    415 
    416         // Camera ID -> Status map
    417         private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>();
    418 
    419         private static final String TAG = "CameraServiceListener";
    420 
    421         @Override
    422         public IBinder asBinder() {
    423             return this;
    424         }
    425 
    426         private boolean isAvailable(int status) {
    427             switch (status) {
    428                 case STATUS_PRESENT:
    429                     return true;
    430                 default:
    431                     return false;
    432             }
    433         }
    434 
    435         private boolean validStatus(int status) {
    436             switch (status) {
    437                 case STATUS_NOT_PRESENT:
    438                 case STATUS_PRESENT:
    439                 case STATUS_ENUMERATING:
    440                 case STATUS_NOT_AVAILABLE:
    441                     return true;
    442                 default:
    443                     return false;
    444             }
    445         }
    446 
    447         @Override
    448         public void onStatusChanged(int status, int cameraId) throws RemoteException {
    449             synchronized(CameraManager.this.mLock) {
    450 
    451                 Log.v(TAG,
    452                         String.format("Camera id %d has status changed to 0x%x", cameraId, status));
    453 
    454                 final String id = String.valueOf(cameraId);
    455 
    456                 if (!validStatus(status)) {
    457                     Log.e(TAG, String.format("Ignoring invalid device %d status 0x%x", cameraId,
    458                             status));
    459                     return;
    460                 }
    461 
    462                 Integer oldStatus = mDeviceStatus.put(id, status);
    463 
    464                 if (oldStatus != null && oldStatus == status) {
    465                     Log.v(TAG, String.format(
    466                             "Device status changed to 0x%x, which is what it already was",
    467                             status));
    468                     return;
    469                 }
    470 
    471                 // TODO: consider abstracting out this state minimization + transition
    472                 // into a separate
    473                 // more easily testable class
    474                 // i.e. (new State()).addState(STATE_AVAILABLE)
    475                 //                   .addState(STATE_NOT_AVAILABLE)
    476                 //                   .addTransition(STATUS_PRESENT, STATE_AVAILABLE),
    477                 //                   .addTransition(STATUS_NOT_PRESENT, STATE_NOT_AVAILABLE)
    478                 //                   .addTransition(STATUS_ENUMERATING, STATE_NOT_AVAILABLE);
    479                 //                   .addTransition(STATUS_NOT_AVAILABLE, STATE_NOT_AVAILABLE);
    480 
    481                 // Translate all the statuses to either 'available' or 'not available'
    482                 //  available -> available         => no new update
    483                 //  not available -> not available => no new update
    484                 if (oldStatus != null && isAvailable(status) == isAvailable(oldStatus)) {
    485 
    486                     Log.v(TAG,
    487                             String.format(
    488                                     "Device status was previously available (%d), " +
    489                                             " and is now again available (%d)" +
    490                                             "so no new client visible update will be sent",
    491                                     isAvailable(status), isAvailable(status)));
    492                     return;
    493                 }
    494 
    495                 final int listenerCount = mListenerMap.size();
    496                 for (int i = 0; i < listenerCount; i++) {
    497                     Handler handler = mListenerMap.valueAt(i);
    498                     final AvailabilityListener listener = mListenerMap.keyAt(i);
    499                     if (isAvailable(status)) {
    500                         handler.post(
    501                             new Runnable() {
    502                                 @Override
    503                                 public void run() {
    504                                     listener.onCameraAvailable(id);
    505                                 }
    506                             });
    507                     } else {
    508                         handler.post(
    509                             new Runnable() {
    510                                 @Override
    511                                 public void run() {
    512                                     listener.onCameraUnavailable(id);
    513                                 }
    514                             });
    515                     }
    516                 } // for
    517             } // synchronized
    518         } // onStatusChanged
    519     } // CameraServiceListener
    520 } // CameraManager
    521