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 android.hardware.camera2.legacy;
18 
19 import android.hardware.ICameraService;
20 import android.hardware.Camera;
21 import android.hardware.Camera.CameraInfo;
22 import android.hardware.camera2.CameraAccessException;
23 import android.hardware.camera2.CameraCharacteristics;
24 import android.hardware.camera2.CaptureRequest;
25 import android.hardware.camera2.ICameraDeviceCallbacks;
26 import android.hardware.camera2.ICameraDeviceUser;
27 import android.hardware.camera2.ICameraOfflineSession;
28 import android.hardware.camera2.impl.CameraMetadataNative;
29 import android.hardware.camera2.impl.CaptureResultExtras;
30 import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
31 import android.hardware.camera2.params.OutputConfiguration;
32 import android.hardware.camera2.params.SessionConfiguration;
33 import android.hardware.camera2.utils.SubmitInfo;
34 import android.os.ConditionVariable;
35 import android.os.IBinder;
36 import android.os.Looper;
37 import android.os.Handler;
38 import android.os.HandlerThread;
39 import android.os.Message;
40 import android.os.RemoteException;
41 import android.os.ServiceSpecificException;
42 import android.util.Log;
43 import android.util.Size;
44 import android.util.SparseArray;
45 import android.view.Surface;
46 
47 import java.util.ArrayList;
48 import java.util.List;
49 
50 import static android.system.OsConstants.EACCES;
51 import static android.system.OsConstants.ENODEV;
52 
53 /**
54  * Compatibility implementation of the Camera2 API binder interface.
55  *
56  * <p>
57  * This is intended to be called from the same process as client
58  * {@link android.hardware.camera2.CameraDevice}, and wraps a
59  * {@link android.hardware.camera2.legacy.LegacyCameraDevice} that emulates Camera2 service using
60  * the Camera1 API.
61  * </p>
62  *
63  * <p>
64  * Keep up to date with ICameraDeviceUser.aidl.
65  * </p>
66  */
67 @SuppressWarnings("deprecation")
68 public class CameraDeviceUserShim implements ICameraDeviceUser {
69     private static final String TAG = "CameraDeviceUserShim";
70 
71     private static final boolean DEBUG = false;
72     private static final int OPEN_CAMERA_TIMEOUT_MS = 5000; // 5 sec (same as api1 cts timeout)
73 
74     private final LegacyCameraDevice mLegacyDevice;
75 
76     private final Object mConfigureLock = new Object();
77     private int mSurfaceIdCounter;
78     private boolean mConfiguring;
79     private final SparseArray<Surface> mSurfaces;
80     private final CameraCharacteristics mCameraCharacteristics;
81     private final CameraLooper mCameraInit;
82     private final CameraCallbackThread mCameraCallbacks;
83 
84 
CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera, CameraCharacteristics characteristics, CameraLooper cameraInit, CameraCallbackThread cameraCallbacks)85     protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera,
86             CameraCharacteristics characteristics, CameraLooper cameraInit,
87             CameraCallbackThread cameraCallbacks) {
88         mLegacyDevice = legacyCamera;
89         mConfiguring = false;
90         mSurfaces = new SparseArray<Surface>();
91         mCameraCharacteristics = characteristics;
92         mCameraInit = cameraInit;
93         mCameraCallbacks = cameraCallbacks;
94 
95         mSurfaceIdCounter = 0;
96     }
97 
translateErrorsFromCamera1(int errorCode)98     private static int translateErrorsFromCamera1(int errorCode) {
99         if (errorCode == -EACCES) {
100             return ICameraService.ERROR_PERMISSION_DENIED;
101         }
102 
103         return errorCode;
104     }
105 
106     /**
107      * Create a separate looper/thread for the camera to run on; open the camera.
108      *
109      * <p>Since the camera automatically latches on to the current thread's looper,
110      * it's important that we have our own thread with our own looper to guarantee
111      * that the camera callbacks get correctly posted to our own thread.</p>
112      */
113     private static class CameraLooper implements Runnable, AutoCloseable {
114         private final int mCameraId;
115         private Looper mLooper;
116         private volatile int mInitErrors;
117         private final Camera mCamera = Camera.openUninitialized();
118         private final ConditionVariable mStartDone = new ConditionVariable();
119         private final Thread mThread;
120 
121         /**
122          * Spin up a new thread, immediately open the camera in the background.
123          *
124          * <p>Use {@link #waitForOpen} to block until the camera is finished opening.</p>
125          *
126          * @param cameraId numeric camera Id
127          *
128          * @see #waitForOpen
129          */
CameraLooper(int cameraId)130         public CameraLooper(int cameraId) {
131             mCameraId = cameraId;
132 
133             mThread = new Thread(this, "LegacyCameraLooper");
134             mThread.start();
135         }
136 
getCamera()137         public Camera getCamera() {
138             return mCamera;
139         }
140 
141         @Override
run()142         public void run() {
143             // Set up a looper to be used by camera.
144             Looper.prepare();
145 
146             // Save the looper so that we can terminate this thread
147             // after we are done with it.
148             mLooper = Looper.myLooper();
149             mInitErrors = mCamera.cameraInitUnspecified(mCameraId);
150             mStartDone.open();
151             Looper.loop();  // Blocks forever until #close is called.
152         }
153 
154         /**
155          * Quit the looper safely; then join until the thread shuts down.
156          */
157         @Override
close()158         public void close() {
159             if (mLooper == null) {
160                 return;
161             }
162 
163             mLooper.quitSafely();
164             try {
165                 mThread.join();
166             } catch (InterruptedException e) {
167                 throw new AssertionError(e);
168             }
169 
170             mLooper = null;
171         }
172 
173         /**
174          * Block until the camera opens; then return its initialization error code (if any).
175          *
176          * @param timeoutMs timeout in milliseconds
177          *
178          * @return int error code
179          *
180          * @throws ServiceSpecificException if the camera open times out with ({@code CAMERA_ERROR})
181          */
waitForOpen(int timeoutMs)182         public int waitForOpen(int timeoutMs) {
183             // Block until the camera is open asynchronously
184             if (!mStartDone.block(timeoutMs)) {
185                 Log.e(TAG, "waitForOpen - Camera failed to open after timeout of "
186                         + OPEN_CAMERA_TIMEOUT_MS + " ms");
187                 try {
188                     mCamera.release();
189                 } catch (RuntimeException e) {
190                     Log.e(TAG, "connectBinderShim - Failed to release camera after timeout ", e);
191                 }
192 
193                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION);
194             }
195 
196             return mInitErrors;
197         }
198     }
199 
200     /**
201      * A thread to process callbacks to send back to the camera client.
202      *
203      * <p>This effectively emulates one-way binder semantics when in the same process as the
204      * callee.</p>
205      */
206     private static class CameraCallbackThread implements ICameraDeviceCallbacks {
207         private static final int CAMERA_ERROR = 0;
208         private static final int CAMERA_IDLE = 1;
209         private static final int CAPTURE_STARTED = 2;
210         private static final int RESULT_RECEIVED = 3;
211         private static final int PREPARED = 4;
212         private static final int REPEATING_REQUEST_ERROR = 5;
213         private static final int REQUEST_QUEUE_EMPTY = 6;
214 
215         private final HandlerThread mHandlerThread;
216         private Handler mHandler;
217 
218         private final ICameraDeviceCallbacks mCallbacks;
219 
CameraCallbackThread(ICameraDeviceCallbacks callbacks)220         public CameraCallbackThread(ICameraDeviceCallbacks callbacks) {
221             mCallbacks = callbacks;
222 
223             mHandlerThread = new HandlerThread("LegacyCameraCallback");
224             mHandlerThread.start();
225         }
226 
close()227         public void close() {
228             mHandlerThread.quitSafely();
229         }
230 
231         @Override
onDeviceError(final int errorCode, final CaptureResultExtras resultExtras)232         public void onDeviceError(final int errorCode, final CaptureResultExtras resultExtras) {
233             Message msg = getHandler().obtainMessage(CAMERA_ERROR,
234                 /*arg1*/ errorCode, /*arg2*/ 0,
235                 /*obj*/ resultExtras);
236             getHandler().sendMessage(msg);
237         }
238 
239         @Override
onDeviceIdle()240         public void onDeviceIdle() {
241             Message msg = getHandler().obtainMessage(CAMERA_IDLE);
242             getHandler().sendMessage(msg);
243         }
244 
245         @Override
onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp)246         public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
247             Message msg = getHandler().obtainMessage(CAPTURE_STARTED,
248                     /*arg1*/ (int) (timestamp & 0xFFFFFFFFL),
249                     /*arg2*/ (int) ( (timestamp >> 32) & 0xFFFFFFFFL),
250                     /*obj*/ resultExtras);
251             getHandler().sendMessage(msg);
252         }
253 
254         @Override
onResultReceived(final CameraMetadataNative result, final CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])255         public void onResultReceived(final CameraMetadataNative result,
256                 final CaptureResultExtras resultExtras,
257                 PhysicalCaptureResultInfo physicalResults[]) {
258             Object[] resultArray = new Object[] { result, resultExtras };
259             Message msg = getHandler().obtainMessage(RESULT_RECEIVED,
260                     /*obj*/ resultArray);
261             getHandler().sendMessage(msg);
262         }
263 
264         @Override
onPrepared(int streamId)265         public void onPrepared(int streamId) {
266             Message msg = getHandler().obtainMessage(PREPARED,
267                     /*arg1*/ streamId, /*arg2*/ 0);
268             getHandler().sendMessage(msg);
269         }
270 
271         @Override
onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId)272         public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) {
273             Object[] objArray = new Object[] { lastFrameNumber, repeatingRequestId };
274             Message msg = getHandler().obtainMessage(REPEATING_REQUEST_ERROR,
275                     /*obj*/ objArray);
276             getHandler().sendMessage(msg);
277         }
278 
279         @Override
onRequestQueueEmpty()280         public void onRequestQueueEmpty() {
281             Message msg = getHandler().obtainMessage(REQUEST_QUEUE_EMPTY,
282                     /* arg1 */ 0, /* arg2 */ 0);
283             getHandler().sendMessage(msg);
284         }
285 
286         @Override
asBinder()287         public IBinder asBinder() {
288             // This is solely intended to be used for in-process binding.
289             return null;
290         }
291 
getHandler()292         private Handler getHandler() {
293             if (mHandler == null) {
294                 mHandler = new CallbackHandler(mHandlerThread.getLooper());
295             }
296             return mHandler;
297         }
298 
299         private class CallbackHandler extends Handler {
CallbackHandler(Looper l)300             public CallbackHandler(Looper l) {
301                 super(l);
302             }
303 
304             @Override
handleMessage(Message msg)305             public void handleMessage(Message msg) {
306                 try {
307                     switch (msg.what) {
308                         case CAMERA_ERROR: {
309                             int errorCode = msg.arg1;
310                             CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj;
311                             mCallbacks.onDeviceError(errorCode, resultExtras);
312                             break;
313                         }
314                         case CAMERA_IDLE:
315                             mCallbacks.onDeviceIdle();
316                             break;
317                         case CAPTURE_STARTED: {
318                             long timestamp = msg.arg2 & 0xFFFFFFFFL;
319                             timestamp = (timestamp << 32) | (msg.arg1 & 0xFFFFFFFFL);
320                             CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj;
321                             mCallbacks.onCaptureStarted(resultExtras, timestamp);
322                             break;
323                         }
324                         case RESULT_RECEIVED: {
325                             Object[] resultArray = (Object[]) msg.obj;
326                             CameraMetadataNative result = (CameraMetadataNative) resultArray[0];
327                             CaptureResultExtras resultExtras = (CaptureResultExtras) resultArray[1];
328                             mCallbacks.onResultReceived(result, resultExtras,
329                                     new PhysicalCaptureResultInfo[0]);
330                             break;
331                         }
332                         case PREPARED: {
333                             int streamId = msg.arg1;
334                             mCallbacks.onPrepared(streamId);
335                             break;
336                         }
337                         case REPEATING_REQUEST_ERROR: {
338                             Object[] objArray = (Object[]) msg.obj;
339                             long lastFrameNumber = (Long) objArray[0];
340                             int repeatingRequestId = (Integer) objArray[1];
341                             mCallbacks.onRepeatingRequestError(lastFrameNumber, repeatingRequestId);
342                             break;
343                         }
344                         case REQUEST_QUEUE_EMPTY: {
345                             mCallbacks.onRequestQueueEmpty();
346                             break;
347                         }
348                         default:
349                             throw new IllegalArgumentException(
350                                 "Unknown callback message " + msg.what);
351                     }
352                 } catch (RemoteException e) {
353                     throw new IllegalStateException(
354                         "Received remote exception during camera callback " + msg.what, e);
355                 }
356             }
357         }
358     }
359 
connectBinderShim(ICameraDeviceCallbacks callbacks, int cameraId, Size displaySize)360     public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks,
361                                                          int cameraId, Size displaySize) {
362         if (DEBUG) {
363             Log.d(TAG, "Opening shim Camera device");
364         }
365 
366         /*
367          * Put the camera open on a separate thread with its own looper; otherwise
368          * if the main thread is used then the callbacks might never get delivered
369          * (e.g. in CTS which run its own default looper only after tests)
370          */
371 
372         CameraLooper init = new CameraLooper(cameraId);
373 
374         CameraCallbackThread threadCallbacks = new CameraCallbackThread(callbacks);
375 
376         // TODO: Make this async instead of blocking
377         int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS);
378         Camera legacyCamera = init.getCamera();
379 
380         // Check errors old HAL initialization
381         LegacyExceptionUtils.throwOnServiceError(initErrors);
382 
383         // Disable shutter sounds (this will work unconditionally) for api2 clients
384         legacyCamera.disableShutterSound();
385 
386         CameraInfo info = new CameraInfo();
387         Camera.getCameraInfo(cameraId, info);
388 
389         Camera.Parameters legacyParameters = null;
390         try {
391             legacyParameters = legacyCamera.getParameters();
392         } catch (RuntimeException e) {
393             throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION,
394                     "Unable to get initial parameters: " + e.getMessage());
395         }
396 
397         CameraCharacteristics characteristics =
398                 LegacyMetadataMapper.createCharacteristics(legacyParameters, info, cameraId,
399                         displaySize);
400         LegacyCameraDevice device = new LegacyCameraDevice(
401                 cameraId, legacyCamera, characteristics, threadCallbacks);
402         return new CameraDeviceUserShim(cameraId, device, characteristics, init, threadCallbacks);
403     }
404 
405     @Override
disconnect()406     public void disconnect() {
407         if (DEBUG) {
408             Log.d(TAG, "disconnect called.");
409         }
410 
411         if (mLegacyDevice.isClosed()) {
412             Log.w(TAG, "Cannot disconnect, device has already been closed.");
413         }
414 
415         try {
416             mLegacyDevice.close();
417         } finally {
418             mCameraInit.close();
419             mCameraCallbacks.close();
420         }
421     }
422 
423     @Override
submitRequest(CaptureRequest request, boolean streaming)424     public SubmitInfo submitRequest(CaptureRequest request, boolean streaming) {
425         if (DEBUG) {
426             Log.d(TAG, "submitRequest called.");
427         }
428         if (mLegacyDevice.isClosed()) {
429             String err = "Cannot submit request, device has been closed.";
430             Log.e(TAG, err);
431             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
432         }
433 
434         synchronized(mConfigureLock) {
435             if (mConfiguring) {
436                 String err = "Cannot submit request, configuration change in progress.";
437                 Log.e(TAG, err);
438                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
439             }
440         }
441         return mLegacyDevice.submitRequest(request, streaming);
442     }
443 
444     @Override
submitRequestList(CaptureRequest[] request, boolean streaming)445     public SubmitInfo submitRequestList(CaptureRequest[] request, boolean streaming) {
446         if (DEBUG) {
447             Log.d(TAG, "submitRequestList called.");
448         }
449         if (mLegacyDevice.isClosed()) {
450             String err = "Cannot submit request list, device has been closed.";
451             Log.e(TAG, err);
452             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
453         }
454 
455         synchronized(mConfigureLock) {
456             if (mConfiguring) {
457                 String err = "Cannot submit request, configuration change in progress.";
458                 Log.e(TAG, err);
459                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
460             }
461         }
462         return mLegacyDevice.submitRequestList(request, streaming);
463     }
464 
465     @Override
cancelRequest(int requestId)466     public long cancelRequest(int requestId) {
467         if (DEBUG) {
468             Log.d(TAG, "cancelRequest called.");
469         }
470         if (mLegacyDevice.isClosed()) {
471             String err = "Cannot cancel request, device has been closed.";
472             Log.e(TAG, err);
473             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
474         }
475 
476         synchronized(mConfigureLock) {
477             if (mConfiguring) {
478                 String err = "Cannot cancel request, configuration change in progress.";
479                 Log.e(TAG, err);
480                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
481             }
482         }
483         return mLegacyDevice.cancelRequest(requestId);
484     }
485 
486     @Override
isSessionConfigurationSupported(SessionConfiguration sessionConfig)487     public boolean isSessionConfigurationSupported(SessionConfiguration sessionConfig) {
488         if (sessionConfig.getSessionType() != SessionConfiguration.SESSION_REGULAR) {
489             Log.e(TAG, "Session type: " + sessionConfig.getSessionType() + " is different from " +
490                     " regular. Legacy devices support only regular session types!");
491             return false;
492         }
493 
494         if (sessionConfig.getInputConfiguration() != null) {
495             Log.e(TAG, "Input configuration present, legacy devices do not support this feature!");
496             return false;
497         }
498 
499         List<OutputConfiguration> outputConfigs = sessionConfig.getOutputConfigurations();
500         if (outputConfigs.isEmpty()) {
501             Log.e(TAG, "Empty output configuration list!");
502             return false;
503         }
504 
505         SparseArray<Surface> surfaces = new SparseArray<Surface>(outputConfigs.size());
506         int idx = 0;
507         for (OutputConfiguration outputConfig : outputConfigs) {
508             List<Surface> surfaceList = outputConfig.getSurfaces();
509             if (surfaceList.isEmpty() || (surfaceList.size() > 1)) {
510                 Log.e(TAG, "Legacy devices do not support deferred or shared surfaces!");
511                 return false;
512             }
513 
514             surfaces.put(idx++, outputConfig.getSurface());
515         }
516 
517         int ret = mLegacyDevice.configureOutputs(surfaces, /*validateSurfacesOnly*/true);
518 
519         return ret == LegacyExceptionUtils.NO_ERROR;
520     }
521 
522     @Override
beginConfigure()523     public void beginConfigure() {
524         if (DEBUG) {
525             Log.d(TAG, "beginConfigure called.");
526         }
527         if (mLegacyDevice.isClosed()) {
528             String err = "Cannot begin configure, device has been closed.";
529             Log.e(TAG, err);
530             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
531         }
532 
533         synchronized(mConfigureLock) {
534             if (mConfiguring) {
535                 String err = "Cannot begin configure, configuration change already in progress.";
536                 Log.e(TAG, err);
537                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
538             }
539             mConfiguring = true;
540         }
541     }
542 
543     @Override
endConfigure(int operatingMode, CameraMetadataNative sessionParams)544     public int[] endConfigure(int operatingMode, CameraMetadataNative sessionParams) {
545         if (DEBUG) {
546             Log.d(TAG, "endConfigure called.");
547         }
548         if (mLegacyDevice.isClosed()) {
549             String err = "Cannot end configure, device has been closed.";
550             Log.e(TAG, err);
551             synchronized(mConfigureLock) {
552                 mConfiguring = false;
553             }
554             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
555         }
556 
557         if (operatingMode != ICameraDeviceUser.NORMAL_MODE) {
558             String err = "LEGACY devices do not support this operating mode";
559             Log.e(TAG, err);
560             synchronized(mConfigureLock) {
561                 mConfiguring = false;
562             }
563             throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
564         }
565 
566         SparseArray<Surface> surfaces = null;
567         synchronized(mConfigureLock) {
568             if (!mConfiguring) {
569                 String err = "Cannot end configure, no configuration change in progress.";
570                 Log.e(TAG, err);
571                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
572             }
573             if (mSurfaces != null) {
574                 surfaces = mSurfaces.clone();
575             }
576             mConfiguring = false;
577         }
578         mLegacyDevice.configureOutputs(surfaces);
579 
580         return new int[0]; // Offline mode is not supported
581     }
582 
583     @Override
deleteStream(int streamId)584     public void deleteStream(int streamId) {
585         if (DEBUG) {
586             Log.d(TAG, "deleteStream called.");
587         }
588         if (mLegacyDevice.isClosed()) {
589             String err = "Cannot delete stream, device has been closed.";
590             Log.e(TAG, err);
591             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
592         }
593 
594         synchronized(mConfigureLock) {
595             if (!mConfiguring) {
596                 String err = "Cannot delete stream, no configuration change in progress.";
597                 Log.e(TAG, err);
598                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
599             }
600             int index = mSurfaces.indexOfKey(streamId);
601             if (index < 0) {
602                 String err = "Cannot delete stream, stream id " + streamId + " doesn't exist.";
603                 Log.e(TAG, err);
604                 throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
605             }
606             mSurfaces.removeAt(index);
607         }
608     }
609 
610     @Override
createStream(OutputConfiguration outputConfiguration)611     public int createStream(OutputConfiguration outputConfiguration) {
612         if (DEBUG) {
613             Log.d(TAG, "createStream called.");
614         }
615         if (mLegacyDevice.isClosed()) {
616             String err = "Cannot create stream, device has been closed.";
617             Log.e(TAG, err);
618             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
619         }
620 
621         synchronized(mConfigureLock) {
622             if (!mConfiguring) {
623                 String err = "Cannot create stream, beginConfigure hasn't been called yet.";
624                 Log.e(TAG, err);
625                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
626             }
627             if (outputConfiguration.getRotation() != OutputConfiguration.ROTATION_0) {
628                 String err = "Cannot create stream, stream rotation is not supported.";
629                 Log.e(TAG, err);
630                 throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
631             }
632             int id = ++mSurfaceIdCounter;
633             mSurfaces.put(id, outputConfiguration.getSurface());
634             return id;
635         }
636     }
637 
638     @Override
finalizeOutputConfigurations(int steamId, OutputConfiguration config)639     public void finalizeOutputConfigurations(int steamId, OutputConfiguration config) {
640         String err = "Finalizing output configuration is not supported on legacy devices";
641         Log.e(TAG, err);
642         throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
643     }
644 
645     @Override
createInputStream(int width, int height, int format)646     public int createInputStream(int width, int height, int format) {
647         String err = "Creating input stream is not supported on legacy devices";
648         Log.e(TAG, err);
649         throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
650     }
651 
652     @Override
getInputSurface()653     public Surface getInputSurface() {
654         String err = "Getting input surface is not supported on legacy devices";
655         Log.e(TAG, err);
656         throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
657     }
658 
659     @Override
createDefaultRequest(int templateId)660     public CameraMetadataNative createDefaultRequest(int templateId) {
661         if (DEBUG) {
662             Log.d(TAG, "createDefaultRequest called.");
663         }
664         if (mLegacyDevice.isClosed()) {
665             String err = "Cannot create default request, device has been closed.";
666             Log.e(TAG, err);
667             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
668         }
669 
670         CameraMetadataNative template;
671         try {
672             template =
673                     LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId);
674         } catch (IllegalArgumentException e) {
675             String err = "createDefaultRequest - invalid templateId specified";
676             Log.e(TAG, err);
677             throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
678         }
679 
680         return template;
681     }
682 
683     @Override
getCameraInfo()684     public CameraMetadataNative getCameraInfo() {
685         if (DEBUG) {
686             Log.d(TAG, "getCameraInfo called.");
687         }
688         // TODO: implement getCameraInfo.
689         Log.e(TAG, "getCameraInfo unimplemented.");
690         return null;
691     }
692 
693     @Override
updateOutputConfiguration(int streamId, OutputConfiguration config)694     public void updateOutputConfiguration(int streamId, OutputConfiguration config) {
695         // TODO: b/63912484 implement updateOutputConfiguration.
696     }
697 
698     @Override
waitUntilIdle()699     public void waitUntilIdle() throws RemoteException {
700         if (DEBUG) {
701             Log.d(TAG, "waitUntilIdle called.");
702         }
703         if (mLegacyDevice.isClosed()) {
704             String err = "Cannot wait until idle, device has been closed.";
705             Log.e(TAG, err);
706             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
707         }
708 
709         synchronized(mConfigureLock) {
710             if (mConfiguring) {
711                 String err = "Cannot wait until idle, configuration change in progress.";
712                 Log.e(TAG, err);
713                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
714             }
715         }
716         mLegacyDevice.waitUntilIdle();
717     }
718 
719     @Override
flush()720     public long flush() {
721         if (DEBUG) {
722             Log.d(TAG, "flush called.");
723         }
724         if (mLegacyDevice.isClosed()) {
725             String err = "Cannot flush, device has been closed.";
726             Log.e(TAG, err);
727             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
728         }
729 
730         synchronized(mConfigureLock) {
731             if (mConfiguring) {
732                 String err = "Cannot flush, configuration change in progress.";
733                 Log.e(TAG, err);
734                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
735             }
736         }
737         return mLegacyDevice.flush();
738     }
739 
prepare(int streamId)740     public void prepare(int streamId) {
741         if (DEBUG) {
742             Log.d(TAG, "prepare called.");
743         }
744         if (mLegacyDevice.isClosed()) {
745             String err = "Cannot prepare stream, device has been closed.";
746             Log.e(TAG, err);
747             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
748         }
749 
750         // LEGACY doesn't support actual prepare, just signal success right away
751         mCameraCallbacks.onPrepared(streamId);
752     }
753 
prepare2(int maxCount, int streamId)754     public void prepare2(int maxCount, int streamId) {
755         // We don't support this in LEGACY mode.
756         prepare(streamId);
757     }
758 
tearDown(int streamId)759     public void tearDown(int streamId) {
760         if (DEBUG) {
761             Log.d(TAG, "tearDown called.");
762         }
763         if (mLegacyDevice.isClosed()) {
764             String err = "Cannot tear down stream, device has been closed.";
765             Log.e(TAG, err);
766             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
767         }
768 
769         // LEGACY doesn't support actual teardown, so just a no-op
770     }
771 
772     @Override
setCameraAudioRestriction(int mode)773     public void setCameraAudioRestriction(int mode) {
774         if (mLegacyDevice.isClosed()) {
775             String err = "Cannot set camera audio restriction, device has been closed.";
776             Log.e(TAG, err);
777             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
778         }
779 
780         mLegacyDevice.setAudioRestriction(mode);
781     }
782 
783     @Override
getGlobalAudioRestriction()784     public int getGlobalAudioRestriction() {
785         if (mLegacyDevice.isClosed()) {
786             String err = "Cannot set camera audio restriction, device has been closed.";
787             Log.e(TAG, err);
788             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
789         }
790 
791         return mLegacyDevice.getAudioRestriction();
792     }
793 
794     @Override
switchToOffline(ICameraDeviceCallbacks cbs, int[] offlineOutputIds)795     public ICameraOfflineSession switchToOffline(ICameraDeviceCallbacks cbs,
796             int[] offlineOutputIds) {
797         throw new UnsupportedOperationException("Legacy device does not support offline mode");
798     }
799 
800     @Override
asBinder()801     public IBinder asBinder() {
802         // This is solely intended to be used for in-process binding.
803         return null;
804     }
805 }
806