1 /* 2 * Copyright 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.cts; 18 19 import android.graphics.Bitmap; 20 import android.graphics.BitmapFactory; 21 import android.graphics.ImageFormat; 22 import android.graphics.PointF; 23 import android.graphics.Rect; 24 import android.hardware.camera2.CameraAccessException; 25 import android.hardware.camera2.CameraCaptureSession; 26 import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession; 27 import android.hardware.camera2.CameraDevice; 28 import android.hardware.camera2.CameraManager; 29 import android.hardware.camera2.CameraCharacteristics; 30 import android.hardware.camera2.CaptureFailure; 31 import android.hardware.camera2.CaptureRequest; 32 import android.hardware.camera2.CaptureResult; 33 import android.hardware.camera2.cts.helpers.CameraErrorCollector; 34 import android.hardware.camera2.cts.helpers.StaticMetadata; 35 import android.hardware.camera2.params.InputConfiguration; 36 import android.hardware.camera2.TotalCaptureResult; 37 import android.hardware.cts.helpers.CameraUtils; 38 import android.hardware.camera2.params.MeteringRectangle; 39 import android.hardware.camera2.params.OutputConfiguration; 40 import android.hardware.camera2.params.StreamConfigurationMap; 41 import android.location.Location; 42 import android.location.LocationManager; 43 import android.media.ExifInterface; 44 import android.media.Image; 45 import android.media.ImageReader; 46 import android.media.ImageWriter; 47 import android.media.Image.Plane; 48 import android.os.Build; 49 import android.os.Environment; 50 import android.os.Handler; 51 import android.util.Log; 52 import android.util.Pair; 53 import android.util.Size; 54 import android.util.Range; 55 import android.view.Display; 56 import android.view.Surface; 57 import android.view.WindowManager; 58 59 import com.android.ex.camera2.blocking.BlockingCameraManager; 60 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException; 61 import com.android.ex.camera2.blocking.BlockingSessionCallback; 62 import com.android.ex.camera2.blocking.BlockingStateCallback; 63 import com.android.ex.camera2.exceptions.TimeoutRuntimeException; 64 65 import junit.framework.Assert; 66 67 import org.mockito.Mockito; 68 69 import java.io.FileOutputStream; 70 import java.io.IOException; 71 import java.lang.reflect.Array; 72 import java.nio.ByteBuffer; 73 import java.util.ArrayList; 74 import java.util.Arrays; 75 import java.util.Collections; 76 import java.util.Comparator; 77 import java.util.Date; 78 import java.util.HashMap; 79 import java.util.List; 80 import java.util.concurrent.atomic.AtomicLong; 81 import java.util.concurrent.LinkedBlockingQueue; 82 import java.util.concurrent.Semaphore; 83 import java.util.concurrent.TimeUnit; 84 import java.text.ParseException; 85 import java.text.SimpleDateFormat; 86 87 /** 88 * A package private utility class for wrapping up the camera2 cts test common utility functions 89 */ 90 public class CameraTestUtils extends Assert { 91 private static final String TAG = "CameraTestUtils"; 92 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 93 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 94 public static final Size SIZE_BOUND_1080P = new Size(1920, 1088); 95 public static final Size SIZE_BOUND_2160P = new Size(3840, 2160); 96 // Only test the preview size that is no larger than 1080p. 97 public static final Size PREVIEW_SIZE_BOUND = SIZE_BOUND_1080P; 98 // Default timeouts for reaching various states 99 public static final int CAMERA_OPEN_TIMEOUT_MS = 3000; 100 public static final int CAMERA_CLOSE_TIMEOUT_MS = 3000; 101 public static final int CAMERA_IDLE_TIMEOUT_MS = 3000; 102 public static final int CAMERA_ACTIVE_TIMEOUT_MS = 1000; 103 public static final int CAMERA_BUSY_TIMEOUT_MS = 1000; 104 public static final int CAMERA_UNCONFIGURED_TIMEOUT_MS = 1000; 105 public static final int CAMERA_CONFIGURE_TIMEOUT_MS = 3000; 106 public static final int CAPTURE_RESULT_TIMEOUT_MS = 3000; 107 public static final int CAPTURE_IMAGE_TIMEOUT_MS = 3000; 108 109 public static final int SESSION_CONFIGURE_TIMEOUT_MS = 3000; 110 public static final int SESSION_CLOSE_TIMEOUT_MS = 3000; 111 public static final int SESSION_READY_TIMEOUT_MS = 3000; 112 public static final int SESSION_ACTIVE_TIMEOUT_MS = 1000; 113 114 public static final int MAX_READER_IMAGES = 5; 115 116 private static final int EXIF_DATETIME_LENGTH = 19; 117 private static final int EXIF_DATETIME_ERROR_MARGIN_SEC = 60; 118 private static final float EXIF_FOCAL_LENGTH_ERROR_MARGIN = 0.001f; 119 private static final float EXIF_EXPOSURE_TIME_ERROR_MARGIN_RATIO = 0.05f; 120 private static final float EXIF_EXPOSURE_TIME_MIN_ERROR_MARGIN_SEC = 0.002f; 121 private static final float EXIF_APERTURE_ERROR_MARGIN = 0.001f; 122 123 private static final Location sTestLocation0 = new Location(LocationManager.GPS_PROVIDER); 124 private static final Location sTestLocation1 = new Location(LocationManager.GPS_PROVIDER); 125 private static final Location sTestLocation2 = new Location(LocationManager.NETWORK_PROVIDER); 126 127 protected static final String DEBUG_FILE_NAME_BASE = 128 Environment.getExternalStorageDirectory().getPath(); 129 130 static { 131 sTestLocation0.setTime(1199145600L); 132 sTestLocation0.setLatitude(37.736071); 133 sTestLocation0.setLongitude(-122.441983); 134 sTestLocation0.setAltitude(21.0); 135 136 sTestLocation1.setTime(1199145601L); 137 sTestLocation1.setLatitude(0.736071); 138 sTestLocation1.setLongitude(0.441983); 139 sTestLocation1.setAltitude(1.0); 140 141 sTestLocation2.setTime(1199145602L); 142 sTestLocation2.setLatitude(-89.736071); 143 sTestLocation2.setLongitude(-179.441983); 144 sTestLocation2.setAltitude(100000.0); 145 } 146 147 // Exif test data vectors. 148 public static final ExifTestData[] EXIF_TEST_DATA = { 149 new ExifTestData( 150 /*gpsLocation*/ sTestLocation0, 151 /* orientation */90, 152 /* jpgQuality */(byte) 80, 153 /* thumbQuality */(byte) 75), 154 new ExifTestData( 155 /*gpsLocation*/ sTestLocation1, 156 /* orientation */180, 157 /* jpgQuality */(byte) 90, 158 /* thumbQuality */(byte) 85), 159 new ExifTestData( 160 /*gpsLocation*/ sTestLocation2, 161 /* orientation */270, 162 /* jpgQuality */(byte) 100, 163 /* thumbQuality */(byte) 100) 164 }; 165 166 /** 167 * Create an {@link android.media.ImageReader} object and get the surface. 168 * 169 * @param size The size of this ImageReader to be created. 170 * @param format The format of this ImageReader to be created 171 * @param maxNumImages The max number of images that can be acquired simultaneously. 172 * @param listener The listener used by this ImageReader to notify callbacks. 173 * @param handler The handler to use for any listener callbacks. 174 */ makeImageReader(Size size, int format, int maxNumImages, ImageReader.OnImageAvailableListener listener, Handler handler)175 public static ImageReader makeImageReader(Size size, int format, int maxNumImages, 176 ImageReader.OnImageAvailableListener listener, Handler handler) { 177 ImageReader reader; 178 reader = ImageReader.newInstance(size.getWidth(), size.getHeight(), format, 179 maxNumImages); 180 reader.setOnImageAvailableListener(listener, handler); 181 if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size); 182 return reader; 183 } 184 185 /** 186 * Create an ImageWriter and hook up the ImageListener. 187 * 188 * @param inputSurface The input surface of the ImageWriter. 189 * @param maxImages The max number of Images that can be dequeued simultaneously. 190 * @param listener The listener used by this ImageWriter to notify callbacks 191 * @param handler The handler to post listener callbacks. 192 * @return ImageWriter object created. 193 */ makeImageWriter( Surface inputSurface, int maxImages, ImageWriter.OnImageReleasedListener listener, Handler handler)194 public static ImageWriter makeImageWriter( 195 Surface inputSurface, int maxImages, 196 ImageWriter.OnImageReleasedListener listener, Handler handler) { 197 ImageWriter writer = ImageWriter.newInstance(inputSurface, maxImages); 198 writer.setOnImageReleasedListener(listener, handler); 199 return writer; 200 } 201 202 /** 203 * Close pending images and clean up an {@link android.media.ImageReader} object. 204 * @param reader an {@link android.media.ImageReader} to close. 205 */ closeImageReader(ImageReader reader)206 public static void closeImageReader(ImageReader reader) { 207 if (reader != null) { 208 reader.close(); 209 } 210 } 211 212 /** 213 * Close pending images and clean up an {@link android.media.ImageWriter} object. 214 * @param writer an {@link android.media.ImageWriter} to close. 215 */ closeImageWriter(ImageWriter writer)216 public static void closeImageWriter(ImageWriter writer) { 217 if (writer != null) { 218 writer.close(); 219 } 220 } 221 222 /** 223 * Dummy listener that release the image immediately once it is available. 224 * 225 * <p> 226 * It can be used for the case where we don't care the image data at all. 227 * </p> 228 */ 229 public static class ImageDropperListener implements ImageReader.OnImageAvailableListener { 230 @Override onImageAvailable(ImageReader reader)231 public void onImageAvailable(ImageReader reader) { 232 Image image = null; 233 try { 234 image = reader.acquireNextImage(); 235 } finally { 236 if (image != null) { 237 image.close(); 238 } 239 } 240 } 241 } 242 243 /** 244 * Image listener that release the image immediately after validating the image 245 */ 246 public static class ImageVerifierListener implements ImageReader.OnImageAvailableListener { 247 private Size mSize; 248 private int mFormat; 249 ImageVerifierListener(Size sz, int format)250 public ImageVerifierListener(Size sz, int format) { 251 mSize = sz; 252 mFormat = format; 253 } 254 255 @Override onImageAvailable(ImageReader reader)256 public void onImageAvailable(ImageReader reader) { 257 Image image = null; 258 try { 259 image = reader.acquireNextImage(); 260 } finally { 261 if (image != null) { 262 // Should only do some quick sanity check in callback, as the ImageReader 263 // could be closed asynchronously, which will close all images acquired from 264 // this ImageReader. 265 checkImage(image, mSize.getWidth(), mSize.getHeight(), mFormat); 266 checkAndroidImageFormat(image); 267 image.close(); 268 } 269 } 270 } 271 } 272 273 public static class SimpleImageReaderListener 274 implements ImageReader.OnImageAvailableListener { 275 private final LinkedBlockingQueue<Image> mQueue = 276 new LinkedBlockingQueue<Image>(); 277 // Indicate whether this listener will drop images or not, 278 // when the queued images reaches the reader maxImages 279 private final boolean mAsyncMode; 280 // maxImages held by the queue in async mode. 281 private final int mMaxImages; 282 283 /** 284 * Create a synchronous SimpleImageReaderListener that queues the images 285 * automatically when they are available, no image will be dropped. If 286 * the caller doesn't call getImage(), the producer will eventually run 287 * into buffer starvation. 288 */ SimpleImageReaderListener()289 public SimpleImageReaderListener() { 290 mAsyncMode = false; 291 mMaxImages = 0; 292 } 293 294 /** 295 * Create a synchronous/asynchronous SimpleImageReaderListener that 296 * queues the images automatically when they are available. For 297 * asynchronous listener, image will be dropped if the queued images 298 * reach to maxImages queued. If the caller doesn't call getImage(), the 299 * producer will not be blocked. For synchronous listener, no image will 300 * be dropped. If the caller doesn't call getImage(), the producer will 301 * eventually run into buffer starvation. 302 * 303 * @param asyncMode If the listener is operating at asynchronous mode. 304 * @param maxImages The max number of images held by this listener. 305 */ 306 /** 307 * 308 * @param asyncMode 309 */ SimpleImageReaderListener(boolean asyncMode, int maxImages)310 public SimpleImageReaderListener(boolean asyncMode, int maxImages) { 311 mAsyncMode = asyncMode; 312 mMaxImages = maxImages; 313 } 314 315 @Override onImageAvailable(ImageReader reader)316 public void onImageAvailable(ImageReader reader) { 317 try { 318 Image imge = reader.acquireNextImage(); 319 if (imge == null) { 320 return; 321 } 322 mQueue.put(imge); 323 if (mAsyncMode && mQueue.size() >= mMaxImages) { 324 Image img = mQueue.poll(); 325 img.close(); 326 } 327 } catch (InterruptedException e) { 328 throw new UnsupportedOperationException( 329 "Can't handle InterruptedException in onImageAvailable"); 330 } 331 } 332 333 /** 334 * Get an image from the image reader. 335 * 336 * @param timeout Timeout value for the wait. 337 * @return The image from the image reader. 338 */ getImage(long timeout)339 public Image getImage(long timeout) throws InterruptedException { 340 Image image = mQueue.poll(timeout, TimeUnit.MILLISECONDS); 341 assertNotNull("Wait for an image timed out in " + timeout + "ms", image); 342 return image; 343 } 344 345 /** 346 * Drain the pending images held by this listener currently. 347 * 348 */ drain()349 public void drain() { 350 while (!mQueue.isEmpty()) { 351 Image image = mQueue.poll(); 352 assertNotNull("Unable to get an image", image); 353 image.close(); 354 } 355 } 356 } 357 358 public static class SimpleImageWriterListener implements ImageWriter.OnImageReleasedListener { 359 private final Semaphore mImageReleasedSema = new Semaphore(0); 360 private final ImageWriter mWriter; 361 @Override onImageReleased(ImageWriter writer)362 public void onImageReleased(ImageWriter writer) { 363 if (writer != mWriter) { 364 return; 365 } 366 367 if (VERBOSE) { 368 Log.v(TAG, "Input image is released"); 369 } 370 mImageReleasedSema.release(); 371 } 372 SimpleImageWriterListener(ImageWriter writer)373 public SimpleImageWriterListener(ImageWriter writer) { 374 if (writer == null) { 375 throw new IllegalArgumentException("writer cannot be null"); 376 } 377 mWriter = writer; 378 } 379 waitForImageReleased(long timeoutMs)380 public void waitForImageReleased(long timeoutMs) throws InterruptedException { 381 if (!mImageReleasedSema.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { 382 fail("wait for image available timed out after " + timeoutMs + "ms"); 383 } 384 } 385 } 386 387 public static class SimpleCaptureCallback extends CameraCaptureSession.CaptureCallback { 388 private final LinkedBlockingQueue<TotalCaptureResult> mQueue = 389 new LinkedBlockingQueue<TotalCaptureResult>(); 390 private final LinkedBlockingQueue<CaptureFailure> mFailureQueue = 391 new LinkedBlockingQueue<>(); 392 // Pair<CaptureRequest, Long> is a pair of capture request and timestamp. 393 private final LinkedBlockingQueue<Pair<CaptureRequest, Long>> mCaptureStartQueue = 394 new LinkedBlockingQueue<>(); 395 // Pair<Int, Long> is a pair of sequence id and frame number 396 private final LinkedBlockingQueue<Pair<Integer, Long>> mCaptureSequenceCompletedQueue = 397 new LinkedBlockingQueue<>(); 398 399 private AtomicLong mNumFramesArrived = new AtomicLong(0); 400 401 @Override onCaptureStarted(CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber)402 public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, 403 long timestamp, long frameNumber) { 404 try { 405 mCaptureStartQueue.put(new Pair(request, timestamp)); 406 } catch (InterruptedException e) { 407 throw new UnsupportedOperationException( 408 "Can't handle InterruptedException in onCaptureStarted"); 409 } 410 } 411 412 @Override onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result)413 public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, 414 TotalCaptureResult result) { 415 try { 416 mNumFramesArrived.incrementAndGet(); 417 mQueue.put(result); 418 } catch (InterruptedException e) { 419 throw new UnsupportedOperationException( 420 "Can't handle InterruptedException in onCaptureCompleted"); 421 } 422 } 423 424 @Override onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure)425 public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, 426 CaptureFailure failure) { 427 try { 428 mFailureQueue.put(failure); 429 } catch (InterruptedException e) { 430 throw new UnsupportedOperationException( 431 "Can't handle InterruptedException in onCaptureFailed"); 432 } 433 } 434 435 @Override onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId, long frameNumber)436 public void onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId, 437 long frameNumber) { 438 try { 439 mCaptureSequenceCompletedQueue.put(new Pair(sequenceId, frameNumber)); 440 } catch (InterruptedException e) { 441 throw new UnsupportedOperationException( 442 "Can't handle InterruptedException in onCaptureSequenceCompleted"); 443 } 444 } 445 getTotalNumFrames()446 public long getTotalNumFrames() { 447 return mNumFramesArrived.get(); 448 } 449 getCaptureResult(long timeout)450 public CaptureResult getCaptureResult(long timeout) { 451 return getTotalCaptureResult(timeout); 452 } 453 getCaptureResult(long timeout, long timestamp)454 public TotalCaptureResult getCaptureResult(long timeout, long timestamp) { 455 try { 456 long currentTs = -1L; 457 TotalCaptureResult result; 458 while (true) { 459 result = mQueue.poll(timeout, TimeUnit.MILLISECONDS); 460 if (result == null) { 461 throw new RuntimeException( 462 "Wait for a capture result timed out in " + timeout + "ms"); 463 } 464 currentTs = result.get(CaptureResult.SENSOR_TIMESTAMP); 465 if (currentTs == timestamp) { 466 return result; 467 } 468 } 469 470 } catch (InterruptedException e) { 471 throw new UnsupportedOperationException("Unhandled interrupted exception", e); 472 } 473 } 474 getTotalCaptureResult(long timeout)475 public TotalCaptureResult getTotalCaptureResult(long timeout) { 476 try { 477 TotalCaptureResult result = mQueue.poll(timeout, TimeUnit.MILLISECONDS); 478 assertNotNull("Wait for a capture result timed out in " + timeout + "ms", result); 479 return result; 480 } catch (InterruptedException e) { 481 throw new UnsupportedOperationException("Unhandled interrupted exception", e); 482 } 483 } 484 485 /** 486 * Get the {@link #CaptureResult capture result} for a given 487 * {@link #CaptureRequest capture request}. 488 * 489 * @param myRequest The {@link #CaptureRequest capture request} whose 490 * corresponding {@link #CaptureResult capture result} was 491 * being waited for 492 * @param numResultsWait Number of frames to wait for the capture result 493 * before timeout. 494 * @throws TimeoutRuntimeException If more than numResultsWait results are 495 * seen before the result matching myRequest arrives, or each 496 * individual wait for result times out after 497 * {@value #CAPTURE_RESULT_TIMEOUT_MS}ms. 498 */ getCaptureResultForRequest(CaptureRequest myRequest, int numResultsWait)499 public CaptureResult getCaptureResultForRequest(CaptureRequest myRequest, 500 int numResultsWait) { 501 return getTotalCaptureResultForRequest(myRequest, numResultsWait); 502 } 503 504 /** 505 * Get the {@link #TotalCaptureResult total capture result} for a given 506 * {@link #CaptureRequest capture request}. 507 * 508 * @param myRequest The {@link #CaptureRequest capture request} whose 509 * corresponding {@link #TotalCaptureResult capture result} was 510 * being waited for 511 * @param numResultsWait Number of frames to wait for the capture result 512 * before timeout. 513 * @throws TimeoutRuntimeException If more than numResultsWait results are 514 * seen before the result matching myRequest arrives, or each 515 * individual wait for result times out after 516 * {@value #CAPTURE_RESULT_TIMEOUT_MS}ms. 517 */ getTotalCaptureResultForRequest(CaptureRequest myRequest, int numResultsWait)518 public TotalCaptureResult getTotalCaptureResultForRequest(CaptureRequest myRequest, 519 int numResultsWait) { 520 ArrayList<CaptureRequest> captureRequests = new ArrayList<>(1); 521 captureRequests.add(myRequest); 522 return getTotalCaptureResultsForRequests(captureRequests, numResultsWait)[0]; 523 } 524 525 /** 526 * Get an array of {@link #TotalCaptureResult total capture results} for a given list of 527 * {@link #CaptureRequest capture requests}. This can be used when the order of results 528 * may not the same as the order of requests. 529 * 530 * @param captureRequests The list of {@link #CaptureRequest capture requests} whose 531 * corresponding {@link #TotalCaptureResult capture results} are 532 * being waited for. 533 * @param numResultsWait Number of frames to wait for the capture results 534 * before timeout. 535 * @throws TimeoutRuntimeException If more than numResultsWait results are 536 * seen before all the results matching captureRequests arrives. 537 */ getTotalCaptureResultsForRequests( List<CaptureRequest> captureRequests, int numResultsWait)538 public TotalCaptureResult[] getTotalCaptureResultsForRequests( 539 List<CaptureRequest> captureRequests, int numResultsWait) { 540 if (numResultsWait < 0) { 541 throw new IllegalArgumentException("numResultsWait must be no less than 0"); 542 } 543 if (captureRequests == null || captureRequests.size() == 0) { 544 throw new IllegalArgumentException("captureRequests must have at least 1 request."); 545 } 546 547 // Create a request -> a list of result indices map that it will wait for. 548 HashMap<CaptureRequest, ArrayList<Integer>> remainingResultIndicesMap = new HashMap<>(); 549 for (int i = 0; i < captureRequests.size(); i++) { 550 CaptureRequest request = captureRequests.get(i); 551 ArrayList<Integer> indices = remainingResultIndicesMap.get(request); 552 if (indices == null) { 553 indices = new ArrayList<>(); 554 remainingResultIndicesMap.put(request, indices); 555 } 556 indices.add(i); 557 } 558 559 TotalCaptureResult[] results = new TotalCaptureResult[captureRequests.size()]; 560 int i = 0; 561 do { 562 TotalCaptureResult result = getTotalCaptureResult(CAPTURE_RESULT_TIMEOUT_MS); 563 CaptureRequest request = result.getRequest(); 564 ArrayList<Integer> indices = remainingResultIndicesMap.get(request); 565 if (indices != null) { 566 results[indices.get(0)] = result; 567 indices.remove(0); 568 569 // Remove the entry if all results for this request has been fulfilled. 570 if (indices.isEmpty()) { 571 remainingResultIndicesMap.remove(request); 572 } 573 } 574 575 if (remainingResultIndicesMap.isEmpty()) { 576 return results; 577 } 578 } while (i++ < numResultsWait); 579 580 throw new TimeoutRuntimeException("Unable to get the expected capture result after " 581 + "waiting for " + numResultsWait + " results"); 582 } 583 584 /** 585 * Get an array list of {@link #CaptureFailure capture failure} with maxNumFailures entries 586 * at most. If it times out before maxNumFailures failures are received, return the failures 587 * received so far. 588 * 589 * @param maxNumFailures The maximal number of failures to return. If it times out before 590 * the maximal number of failures are received, return the received 591 * failures so far. 592 * @throws UnsupportedOperationException If an error happens while waiting on the failure. 593 */ getCaptureFailures(long maxNumFailures)594 public ArrayList<CaptureFailure> getCaptureFailures(long maxNumFailures) { 595 ArrayList<CaptureFailure> failures = new ArrayList<>(); 596 try { 597 for (int i = 0; i < maxNumFailures; i++) { 598 CaptureFailure failure = mFailureQueue.poll(CAPTURE_RESULT_TIMEOUT_MS, 599 TimeUnit.MILLISECONDS); 600 if (failure == null) { 601 // If waiting on a failure times out, return the failures so far. 602 break; 603 } 604 failures.add(failure); 605 } 606 } catch (InterruptedException e) { 607 throw new UnsupportedOperationException("Unhandled interrupted exception", e); 608 } 609 610 return failures; 611 } 612 613 /** 614 * Wait until the capture start of a request and expected timestamp arrives or it times 615 * out after a number of capture starts. 616 * 617 * @param request The request for the capture start to wait for. 618 * @param timestamp The timestamp for the capture start to wait for. 619 * @param numCaptureStartsWait The number of capture start events to wait for before timing 620 * out. 621 */ waitForCaptureStart(CaptureRequest request, Long timestamp, int numCaptureStartsWait)622 public void waitForCaptureStart(CaptureRequest request, Long timestamp, 623 int numCaptureStartsWait) throws Exception { 624 Pair<CaptureRequest, Long> expectedShutter = new Pair<>(request, timestamp); 625 626 int i = 0; 627 do { 628 Pair<CaptureRequest, Long> shutter = mCaptureStartQueue.poll( 629 CAPTURE_RESULT_TIMEOUT_MS, TimeUnit.MILLISECONDS); 630 631 if (shutter == null) { 632 throw new TimeoutRuntimeException("Unable to get any more capture start " + 633 "event after waiting for " + CAPTURE_RESULT_TIMEOUT_MS + " ms."); 634 } else if (expectedShutter.equals(shutter)) { 635 return; 636 } 637 638 } while (i++ < numCaptureStartsWait); 639 640 throw new TimeoutRuntimeException("Unable to get the expected capture start " + 641 "event after waiting for " + numCaptureStartsWait + " capture starts"); 642 } 643 644 /** 645 * Wait until it receives capture sequence completed callback for a given squence ID. 646 * 647 * @param sequenceId The sequence ID of the capture sequence completed callback to wait for. 648 * @param timeoutMs Time to wait for each capture sequence complete callback before 649 * timing out. 650 */ getCaptureSequenceLastFrameNumber(int sequenceId, long timeoutMs)651 public long getCaptureSequenceLastFrameNumber(int sequenceId, long timeoutMs) { 652 try { 653 while (true) { 654 Pair<Integer, Long> completedSequence = 655 mCaptureSequenceCompletedQueue.poll(timeoutMs, TimeUnit.MILLISECONDS); 656 assertNotNull("Wait for a capture sequence completed timed out in " + 657 timeoutMs + "ms", completedSequence); 658 659 if (completedSequence.first.equals(sequenceId)) { 660 return completedSequence.second.longValue(); 661 } 662 } 663 } catch (InterruptedException e) { 664 throw new UnsupportedOperationException("Unhandled interrupted exception", e); 665 } 666 } 667 hasMoreResults()668 public boolean hasMoreResults() 669 { 670 return !mQueue.isEmpty(); 671 } 672 hasMoreFailures()673 public boolean hasMoreFailures() 674 { 675 return !mFailureQueue.isEmpty(); 676 } 677 drain()678 public void drain() { 679 mQueue.clear(); 680 mNumFramesArrived.getAndSet(0); 681 mFailureQueue.clear(); 682 mCaptureStartQueue.clear(); 683 } 684 } 685 686 /** 687 * Block until the camera is opened. 688 * 689 * <p>Don't use this to test #onDisconnected/#onError since this will throw 690 * an AssertionError if it fails to open the camera device.</p> 691 * 692 * @return CameraDevice opened camera device 693 * 694 * @throws IllegalArgumentException 695 * If the handler is null, or if the handler's looper is current. 696 * @throws CameraAccessException 697 * If open fails immediately. 698 * @throws BlockingOpenException 699 * If open fails after blocking for some amount of time. 700 * @throws TimeoutRuntimeException 701 * If opening times out. Typically unrecoverable. 702 */ openCamera(CameraManager manager, String cameraId, CameraDevice.StateCallback listener, Handler handler)703 public static CameraDevice openCamera(CameraManager manager, String cameraId, 704 CameraDevice.StateCallback listener, Handler handler) throws CameraAccessException, 705 BlockingOpenException { 706 707 /** 708 * Although camera2 API allows 'null' Handler (it will just use the current 709 * thread's Looper), this is not what we want for CTS. 710 * 711 * In CTS the default looper is used only to process events in between test runs, 712 * so anything sent there would not be executed inside a test and the test would fail. 713 * 714 * In this case, BlockingCameraManager#openCamera performs the check for us. 715 */ 716 return (new BlockingCameraManager(manager)).openCamera(cameraId, listener, handler); 717 } 718 719 720 /** 721 * Block until the camera is opened. 722 * 723 * <p>Don't use this to test #onDisconnected/#onError since this will throw 724 * an AssertionError if it fails to open the camera device.</p> 725 * 726 * @throws IllegalArgumentException 727 * If the handler is null, or if the handler's looper is current. 728 * @throws CameraAccessException 729 * If open fails immediately. 730 * @throws BlockingOpenException 731 * If open fails after blocking for some amount of time. 732 * @throws TimeoutRuntimeException 733 * If opening times out. Typically unrecoverable. 734 */ openCamera(CameraManager manager, String cameraId, Handler handler)735 public static CameraDevice openCamera(CameraManager manager, String cameraId, Handler handler) 736 throws CameraAccessException, 737 BlockingOpenException { 738 return openCamera(manager, cameraId, /*listener*/null, handler); 739 } 740 741 /** 742 * Configure a new camera session with output surfaces and type. 743 * 744 * @param camera The CameraDevice to be configured. 745 * @param outputSurfaces The surface list that used for camera output. 746 * @param listener The callback CameraDevice will notify when capture results are available. 747 */ configureCameraSession(CameraDevice camera, List<Surface> outputSurfaces, boolean isHighSpeed, CameraCaptureSession.StateCallback listener, Handler handler)748 public static CameraCaptureSession configureCameraSession(CameraDevice camera, 749 List<Surface> outputSurfaces, boolean isHighSpeed, 750 CameraCaptureSession.StateCallback listener, Handler handler) 751 throws CameraAccessException { 752 BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener); 753 if (isHighSpeed) { 754 camera.createConstrainedHighSpeedCaptureSession(outputSurfaces, 755 sessionListener, handler); 756 } else { 757 camera.createCaptureSession(outputSurfaces, sessionListener, handler); 758 } 759 CameraCaptureSession session = 760 sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); 761 assertFalse("Camera session should not be a reprocessable session", 762 session.isReprocessable()); 763 String sessionType = isHighSpeed ? "High Speed" : "Normal"; 764 assertTrue("Capture session type must be " + sessionType, 765 isHighSpeed == 766 CameraConstrainedHighSpeedCaptureSession.class.isAssignableFrom(session.getClass())); 767 768 return session; 769 } 770 771 /** 772 * Configure a new camera session with output configurations. 773 * 774 * @param camera The CameraDevice to be configured. 775 * @param outputs The OutputConfiguration list that is used for camera output. 776 * @param listener The callback CameraDevice will notify when capture results are available. 777 */ configureCameraSessionWithConfig(CameraDevice camera, List<OutputConfiguration> outputs, CameraCaptureSession.StateCallback listener, Handler handler)778 public static CameraCaptureSession configureCameraSessionWithConfig(CameraDevice camera, 779 List<OutputConfiguration> outputs, 780 CameraCaptureSession.StateCallback listener, Handler handler) 781 throws CameraAccessException { 782 BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener); 783 camera.createCaptureSessionByOutputConfigurations(outputs, sessionListener, handler); 784 CameraCaptureSession session = 785 sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); 786 assertFalse("Camera session should not be a reprocessable session", 787 session.isReprocessable()); 788 return session; 789 } 790 791 /** 792 * Configure a new camera session with output surfaces. 793 * 794 * @param camera The CameraDevice to be configured. 795 * @param outputSurfaces The surface list that used for camera output. 796 * @param listener The callback CameraDevice will notify when capture results are available. 797 */ configureCameraSession(CameraDevice camera, List<Surface> outputSurfaces, CameraCaptureSession.StateCallback listener, Handler handler)798 public static CameraCaptureSession configureCameraSession(CameraDevice camera, 799 List<Surface> outputSurfaces, 800 CameraCaptureSession.StateCallback listener, Handler handler) 801 throws CameraAccessException { 802 803 return configureCameraSession(camera, outputSurfaces, /*isHighSpeed*/false, 804 listener, handler); 805 } 806 configureReprocessableCameraSession(CameraDevice camera, InputConfiguration inputConfiguration, List<Surface> outputSurfaces, CameraCaptureSession.StateCallback listener, Handler handler)807 public static CameraCaptureSession configureReprocessableCameraSession(CameraDevice camera, 808 InputConfiguration inputConfiguration, List<Surface> outputSurfaces, 809 CameraCaptureSession.StateCallback listener, Handler handler) 810 throws CameraAccessException { 811 BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener); 812 camera.createReprocessableCaptureSession(inputConfiguration, outputSurfaces, 813 sessionListener, handler); 814 815 Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY, 816 BlockingSessionCallback.SESSION_CONFIGURE_FAILED}; 817 int state = sessionListener.getStateWaiter().waitForAnyOfStates( 818 Arrays.asList(sessionStates), SESSION_CONFIGURE_TIMEOUT_MS); 819 820 assertTrue("Creating a reprocessable session failed.", 821 state == BlockingSessionCallback.SESSION_READY); 822 823 CameraCaptureSession session = 824 sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); 825 assertTrue("Camera session should be a reprocessable session", session.isReprocessable()); 826 827 return session; 828 } 829 830 /** 831 * Create a reprocessable camera session with input and output configurations. 832 * 833 * @param camera The CameraDevice to be configured. 834 * @param inputConfiguration The input configuration used to create this session. 835 * @param outputs The output configurations used to create this session. 836 * @param listener The callback CameraDevice will notify when capture results are available. 837 * @param handler The handler used to notify callbacks. 838 * @return The session ready to use. 839 * @throws CameraAccessException 840 */ configureReprocCameraSessionWithConfig(CameraDevice camera, InputConfiguration inputConfiguration, List<OutputConfiguration> outputs, CameraCaptureSession.StateCallback listener, Handler handler)841 public static CameraCaptureSession configureReprocCameraSessionWithConfig(CameraDevice camera, 842 InputConfiguration inputConfiguration, List<OutputConfiguration> outputs, 843 CameraCaptureSession.StateCallback listener, Handler handler) 844 throws CameraAccessException { 845 BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener); 846 camera.createReprocessableCaptureSessionByConfigurations(inputConfiguration, outputs, 847 sessionListener, handler); 848 849 Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY, 850 BlockingSessionCallback.SESSION_CONFIGURE_FAILED}; 851 int state = sessionListener.getStateWaiter().waitForAnyOfStates( 852 Arrays.asList(sessionStates), SESSION_CONFIGURE_TIMEOUT_MS); 853 854 assertTrue("Creating a reprocessable session failed.", 855 state == BlockingSessionCallback.SESSION_READY); 856 857 CameraCaptureSession session = 858 sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); 859 assertTrue("Camera session should be a reprocessable session", session.isReprocessable()); 860 861 return session; 862 } 863 assertArrayNotEmpty(T arr, String message)864 public static <T> void assertArrayNotEmpty(T arr, String message) { 865 assertTrue(message, arr != null && Array.getLength(arr) > 0); 866 } 867 868 /** 869 * Check if the format is a legal YUV format camera supported. 870 */ checkYuvFormat(int format)871 public static void checkYuvFormat(int format) { 872 if ((format != ImageFormat.YUV_420_888) && 873 (format != ImageFormat.NV21) && 874 (format != ImageFormat.YV12)) { 875 fail("Wrong formats: " + format); 876 } 877 } 878 879 /** 880 * Check if image size and format match given size and format. 881 */ checkImage(Image image, int width, int height, int format)882 public static void checkImage(Image image, int width, int height, int format) { 883 // Image reader will wrap YV12/NV21 image by YUV_420_888 884 if (format == ImageFormat.NV21 || format == ImageFormat.YV12) { 885 format = ImageFormat.YUV_420_888; 886 } 887 assertNotNull("Input image is invalid", image); 888 assertEquals("Format doesn't match", format, image.getFormat()); 889 assertEquals("Width doesn't match", width, image.getWidth()); 890 assertEquals("Height doesn't match", height, image.getHeight()); 891 } 892 893 /** 894 * <p>Read data from all planes of an Image into a contiguous unpadded, unpacked 895 * 1-D linear byte array, such that it can be write into disk, or accessed by 896 * software conveniently. It supports YUV_420_888/NV21/YV12 and JPEG input 897 * Image format.</p> 898 * 899 * <p>For YUV_420_888/NV21/YV12/Y8/Y16, it returns a byte array that contains 900 * the Y plane data first, followed by U(Cb), V(Cr) planes if there is any 901 * (xstride = width, ystride = height for chroma and luma components).</p> 902 * 903 * <p>For JPEG, it returns a 1-D byte array contains a complete JPEG image.</p> 904 */ getDataFromImage(Image image)905 public static byte[] getDataFromImage(Image image) { 906 assertNotNull("Invalid image:", image); 907 int format = image.getFormat(); 908 int width = image.getWidth(); 909 int height = image.getHeight(); 910 int rowStride, pixelStride; 911 byte[] data = null; 912 913 // Read image data 914 Plane[] planes = image.getPlanes(); 915 assertTrue("Fail to get image planes", planes != null && planes.length > 0); 916 917 // Check image validity 918 checkAndroidImageFormat(image); 919 920 ByteBuffer buffer = null; 921 // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer. 922 // Same goes for DEPTH_POINT_CLOUD 923 if (format == ImageFormat.JPEG || format == ImageFormat.DEPTH_POINT_CLOUD || 924 format == ImageFormat.RAW_PRIVATE) { 925 buffer = planes[0].getBuffer(); 926 assertNotNull("Fail to get jpeg or depth ByteBuffer", buffer); 927 data = new byte[buffer.remaining()]; 928 buffer.get(data); 929 buffer.rewind(); 930 return data; 931 } 932 933 int offset = 0; 934 data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8]; 935 int maxRowSize = planes[0].getRowStride(); 936 for (int i = 0; i < planes.length; i++) { 937 if (maxRowSize < planes[i].getRowStride()) { 938 maxRowSize = planes[i].getRowStride(); 939 } 940 } 941 byte[] rowData = new byte[maxRowSize]; 942 if(VERBOSE) Log.v(TAG, "get data from " + planes.length + " planes"); 943 for (int i = 0; i < planes.length; i++) { 944 buffer = planes[i].getBuffer(); 945 assertNotNull("Fail to get bytebuffer from plane", buffer); 946 rowStride = planes[i].getRowStride(); 947 pixelStride = planes[i].getPixelStride(); 948 assertTrue("pixel stride " + pixelStride + " is invalid", pixelStride > 0); 949 if (VERBOSE) { 950 Log.v(TAG, "pixelStride " + pixelStride); 951 Log.v(TAG, "rowStride " + rowStride); 952 Log.v(TAG, "width " + width); 953 Log.v(TAG, "height " + height); 954 } 955 // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling. 956 int w = (i == 0) ? width : width / 2; 957 int h = (i == 0) ? height : height / 2; 958 assertTrue("rowStride " + rowStride + " should be >= width " + w , rowStride >= w); 959 for (int row = 0; row < h; row++) { 960 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8; 961 int length; 962 if (pixelStride == bytesPerPixel) { 963 // Special case: optimized read of the entire row 964 length = w * bytesPerPixel; 965 buffer.get(data, offset, length); 966 offset += length; 967 } else { 968 // Generic case: should work for any pixelStride but slower. 969 // Use intermediate buffer to avoid read byte-by-byte from 970 // DirectByteBuffer, which is very bad for performance 971 length = (w - 1) * pixelStride + bytesPerPixel; 972 buffer.get(rowData, 0, length); 973 for (int col = 0; col < w; col++) { 974 data[offset++] = rowData[col * pixelStride]; 975 } 976 } 977 // Advance buffer the remainder of the row stride 978 if (row < h - 1) { 979 buffer.position(buffer.position() + rowStride - length); 980 } 981 } 982 if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i); 983 buffer.rewind(); 984 } 985 return data; 986 } 987 988 /** 989 * <p>Check android image format validity for an image, only support below formats:</p> 990 * 991 * <p>YUV_420_888/NV21/YV12, can add more for future</p> 992 */ checkAndroidImageFormat(Image image)993 public static void checkAndroidImageFormat(Image image) { 994 int format = image.getFormat(); 995 Plane[] planes = image.getPlanes(); 996 switch (format) { 997 case ImageFormat.YUV_420_888: 998 case ImageFormat.NV21: 999 case ImageFormat.YV12: 1000 assertEquals("YUV420 format Images should have 3 planes", 3, planes.length); 1001 break; 1002 case ImageFormat.JPEG: 1003 case ImageFormat.RAW_SENSOR: 1004 case ImageFormat.RAW_PRIVATE: 1005 case ImageFormat.DEPTH16: 1006 case ImageFormat.DEPTH_POINT_CLOUD: 1007 assertEquals("JPEG/RAW/depth Images should have one plane", 1, planes.length); 1008 break; 1009 default: 1010 fail("Unsupported Image Format: " + format); 1011 } 1012 } 1013 dumpFile(String fileName, Bitmap data)1014 public static void dumpFile(String fileName, Bitmap data) { 1015 FileOutputStream outStream; 1016 try { 1017 Log.v(TAG, "output will be saved as " + fileName); 1018 outStream = new FileOutputStream(fileName); 1019 } catch (IOException ioe) { 1020 throw new RuntimeException("Unable to create debug output file " + fileName, ioe); 1021 } 1022 1023 try { 1024 data.compress(Bitmap.CompressFormat.JPEG, /*quality*/90, outStream); 1025 outStream.close(); 1026 } catch (IOException ioe) { 1027 throw new RuntimeException("failed writing data to file " + fileName, ioe); 1028 } 1029 } 1030 dumpFile(String fileName, byte[] data)1031 public static void dumpFile(String fileName, byte[] data) { 1032 FileOutputStream outStream; 1033 try { 1034 Log.v(TAG, "output will be saved as " + fileName); 1035 outStream = new FileOutputStream(fileName); 1036 } catch (IOException ioe) { 1037 throw new RuntimeException("Unable to create debug output file " + fileName, ioe); 1038 } 1039 1040 try { 1041 outStream.write(data); 1042 outStream.close(); 1043 } catch (IOException ioe) { 1044 throw new RuntimeException("failed writing data to file " + fileName, ioe); 1045 } 1046 } 1047 1048 /** 1049 * Get the available output sizes for the user-defined {@code format}. 1050 * 1051 * <p>Note that implementation-defined/hidden formats are not supported.</p> 1052 */ getSupportedSizeForFormat(int format, String cameraId, CameraManager cameraManager)1053 public static Size[] getSupportedSizeForFormat(int format, String cameraId, 1054 CameraManager cameraManager) throws CameraAccessException { 1055 CameraCharacteristics properties = cameraManager.getCameraCharacteristics(cameraId); 1056 assertNotNull("Can't get camera characteristics!", properties); 1057 if (VERBOSE) { 1058 Log.v(TAG, "get camera characteristics for camera: " + cameraId); 1059 } 1060 StreamConfigurationMap configMap = 1061 properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 1062 Size[] availableSizes = configMap.getOutputSizes(format); 1063 assertArrayNotEmpty(availableSizes, "availableSizes should not be empty for format: " 1064 + format); 1065 Size[] highResAvailableSizes = configMap.getHighResolutionOutputSizes(format); 1066 if (highResAvailableSizes != null && highResAvailableSizes.length > 0) { 1067 Size[] allSizes = new Size[availableSizes.length + highResAvailableSizes.length]; 1068 System.arraycopy(availableSizes, 0, allSizes, 0, 1069 availableSizes.length); 1070 System.arraycopy(highResAvailableSizes, 0, allSizes, availableSizes.length, 1071 highResAvailableSizes.length); 1072 availableSizes = allSizes; 1073 } 1074 if (VERBOSE) Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(availableSizes)); 1075 return availableSizes; 1076 } 1077 1078 /** 1079 * Get the available output sizes for the given class. 1080 * 1081 */ getSupportedSizeForClass(Class klass, String cameraId, CameraManager cameraManager)1082 public static Size[] getSupportedSizeForClass(Class klass, String cameraId, 1083 CameraManager cameraManager) throws CameraAccessException { 1084 CameraCharacteristics properties = cameraManager.getCameraCharacteristics(cameraId); 1085 assertNotNull("Can't get camera characteristics!", properties); 1086 if (VERBOSE) { 1087 Log.v(TAG, "get camera characteristics for camera: " + cameraId); 1088 } 1089 StreamConfigurationMap configMap = 1090 properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 1091 Size[] availableSizes = configMap.getOutputSizes(klass); 1092 assertArrayNotEmpty(availableSizes, "availableSizes should not be empty for class: " 1093 + klass); 1094 Size[] highResAvailableSizes = configMap.getHighResolutionOutputSizes(ImageFormat.PRIVATE); 1095 if (highResAvailableSizes != null && highResAvailableSizes.length > 0) { 1096 Size[] allSizes = new Size[availableSizes.length + highResAvailableSizes.length]; 1097 System.arraycopy(availableSizes, 0, allSizes, 0, 1098 availableSizes.length); 1099 System.arraycopy(highResAvailableSizes, 0, allSizes, availableSizes.length, 1100 highResAvailableSizes.length); 1101 availableSizes = allSizes; 1102 } 1103 if (VERBOSE) Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(availableSizes)); 1104 return availableSizes; 1105 } 1106 1107 /** 1108 * Size comparator that compares the number of pixels it covers. 1109 * 1110 * <p>If two the areas of two sizes are same, compare the widths.</p> 1111 */ 1112 public static class SizeComparator implements Comparator<Size> { 1113 @Override compare(Size lhs, Size rhs)1114 public int compare(Size lhs, Size rhs) { 1115 return CameraUtils 1116 .compareSizes(lhs.getWidth(), lhs.getHeight(), rhs.getWidth(), rhs.getHeight()); 1117 } 1118 } 1119 1120 /** 1121 * Get sorted size list in descending order. Remove the sizes larger than 1122 * the bound. If the bound is null, don't do the size bound filtering. 1123 */ getSupportedPreviewSizes(String cameraId, CameraManager cameraManager, Size bound)1124 static public List<Size> getSupportedPreviewSizes(String cameraId, 1125 CameraManager cameraManager, Size bound) throws CameraAccessException { 1126 1127 Size[] rawSizes = getSupportedSizeForClass(android.view.SurfaceHolder.class, cameraId, 1128 cameraManager); 1129 assertArrayNotEmpty(rawSizes, 1130 "Available sizes for SurfaceHolder class should not be empty"); 1131 if (VERBOSE) { 1132 Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(rawSizes)); 1133 } 1134 1135 if (bound == null) { 1136 return getAscendingOrderSizes(Arrays.asList(rawSizes), /*ascending*/false); 1137 } 1138 1139 List<Size> sizes = new ArrayList<Size>(); 1140 for (Size sz: rawSizes) { 1141 if (sz.getWidth() <= bound.getWidth() && sz.getHeight() <= bound.getHeight()) { 1142 sizes.add(sz); 1143 } 1144 } 1145 return getAscendingOrderSizes(sizes, /*ascending*/false); 1146 } 1147 1148 /** 1149 * Get a sorted list of sizes from a given size list. 1150 * 1151 * <p> 1152 * The size is compare by area it covers, if the areas are same, then 1153 * compare the widths. 1154 * </p> 1155 * 1156 * @param sizeList The input size list to be sorted 1157 * @param ascending True if the order is ascending, otherwise descending order 1158 * @return The ordered list of sizes 1159 */ getAscendingOrderSizes(final List<Size> sizeList, boolean ascending)1160 static public List<Size> getAscendingOrderSizes(final List<Size> sizeList, boolean ascending) { 1161 if (sizeList == null) { 1162 throw new IllegalArgumentException("sizeList shouldn't be null"); 1163 } 1164 1165 Comparator<Size> comparator = new SizeComparator(); 1166 List<Size> sortedSizes = new ArrayList<Size>(); 1167 sortedSizes.addAll(sizeList); 1168 Collections.sort(sortedSizes, comparator); 1169 if (!ascending) { 1170 Collections.reverse(sortedSizes); 1171 } 1172 1173 return sortedSizes; 1174 } 1175 1176 /** 1177 * Get sorted (descending order) size list for given format. Remove the sizes larger than 1178 * the bound. If the bound is null, don't do the size bound filtering. 1179 */ getSortedSizesForFormat(String cameraId, CameraManager cameraManager, int format, Size bound)1180 static public List<Size> getSortedSizesForFormat(String cameraId, 1181 CameraManager cameraManager, int format, Size bound) throws CameraAccessException { 1182 Comparator<Size> comparator = new SizeComparator(); 1183 Size[] sizes = getSupportedSizeForFormat(format, cameraId, cameraManager); 1184 List<Size> sortedSizes = null; 1185 if (bound != null) { 1186 sortedSizes = new ArrayList<Size>(/*capacity*/1); 1187 for (Size sz : sizes) { 1188 if (comparator.compare(sz, bound) <= 0) { 1189 sortedSizes.add(sz); 1190 } 1191 } 1192 } else { 1193 sortedSizes = Arrays.asList(sizes); 1194 } 1195 assertTrue("Supported size list should have at least one element", 1196 sortedSizes.size() > 0); 1197 1198 Collections.sort(sortedSizes, comparator); 1199 // Make it in descending order. 1200 Collections.reverse(sortedSizes); 1201 return sortedSizes; 1202 } 1203 1204 /** 1205 * Get supported video size list for a given camera device. 1206 * 1207 * <p> 1208 * Filter out the sizes that are larger than the bound. If the bound is 1209 * null, don't do the size bound filtering. 1210 * </p> 1211 */ getSupportedVideoSizes(String cameraId, CameraManager cameraManager, Size bound)1212 static public List<Size> getSupportedVideoSizes(String cameraId, 1213 CameraManager cameraManager, Size bound) throws CameraAccessException { 1214 1215 Size[] rawSizes = getSupportedSizeForClass(android.media.MediaRecorder.class, 1216 cameraId, cameraManager); 1217 assertArrayNotEmpty(rawSizes, 1218 "Available sizes for MediaRecorder class should not be empty"); 1219 if (VERBOSE) { 1220 Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(rawSizes)); 1221 } 1222 1223 if (bound == null) { 1224 return getAscendingOrderSizes(Arrays.asList(rawSizes), /*ascending*/false); 1225 } 1226 1227 List<Size> sizes = new ArrayList<Size>(); 1228 for (Size sz: rawSizes) { 1229 if (sz.getWidth() <= bound.getWidth() && sz.getHeight() <= bound.getHeight()) { 1230 sizes.add(sz); 1231 } 1232 } 1233 return getAscendingOrderSizes(sizes, /*ascending*/false); 1234 } 1235 1236 /** 1237 * Get supported video size list (descending order) for a given camera device. 1238 * 1239 * <p> 1240 * Filter out the sizes that are larger than the bound. If the bound is 1241 * null, don't do the size bound filtering. 1242 * </p> 1243 */ getSupportedStillSizes(String cameraId, CameraManager cameraManager, Size bound)1244 static public List<Size> getSupportedStillSizes(String cameraId, 1245 CameraManager cameraManager, Size bound) throws CameraAccessException { 1246 return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.JPEG, bound); 1247 } 1248 getMinPreviewSize(String cameraId, CameraManager cameraManager)1249 static public Size getMinPreviewSize(String cameraId, CameraManager cameraManager) 1250 throws CameraAccessException { 1251 List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, null); 1252 return sizes.get(sizes.size() - 1); 1253 } 1254 1255 /** 1256 * Get max supported preview size for a camera device. 1257 */ getMaxPreviewSize(String cameraId, CameraManager cameraManager)1258 static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager) 1259 throws CameraAccessException { 1260 return getMaxPreviewSize(cameraId, cameraManager, /*bound*/null); 1261 } 1262 1263 /** 1264 * Get max preview size for a camera device in the supported sizes that are no larger 1265 * than the bound. 1266 */ getMaxPreviewSize(String cameraId, CameraManager cameraManager, Size bound)1267 static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager, Size bound) 1268 throws CameraAccessException { 1269 List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, bound); 1270 return sizes.get(0); 1271 } 1272 1273 /** 1274 * Get max depth size for a camera device. 1275 */ getMaxDepthSize(String cameraId, CameraManager cameraManager)1276 static public Size getMaxDepthSize(String cameraId, CameraManager cameraManager) 1277 throws CameraAccessException { 1278 List<Size> sizes = getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.DEPTH16, 1279 /*bound*/ null); 1280 return sizes.get(0); 1281 } 1282 1283 /** 1284 * Get the largest size by area. 1285 * 1286 * @param sizes an array of sizes, must have at least 1 element 1287 * 1288 * @return Largest Size 1289 * 1290 * @throws IllegalArgumentException if sizes was null or had 0 elements 1291 */ getMaxSize(Size... sizes)1292 public static Size getMaxSize(Size... sizes) { 1293 if (sizes == null || sizes.length == 0) { 1294 throw new IllegalArgumentException("sizes was empty"); 1295 } 1296 1297 Size sz = sizes[0]; 1298 for (Size size : sizes) { 1299 if (size.getWidth() * size.getHeight() > sz.getWidth() * sz.getHeight()) { 1300 sz = size; 1301 } 1302 } 1303 1304 return sz; 1305 } 1306 1307 /** 1308 * Returns true if the given {@code array} contains the given element. 1309 * 1310 * @param array {@code array} to check for {@code elem} 1311 * @param elem {@code elem} to test for 1312 * @return {@code true} if the given element is contained 1313 */ contains(int[] array, int elem)1314 public static boolean contains(int[] array, int elem) { 1315 if (array == null) return false; 1316 for (int i = 0; i < array.length; i++) { 1317 if (elem == array[i]) return true; 1318 } 1319 return false; 1320 } 1321 1322 /** 1323 * Get object array from byte array. 1324 * 1325 * @param array Input byte array to be converted 1326 * @return Byte object array converted from input byte array 1327 */ toObject(byte[] array)1328 public static Byte[] toObject(byte[] array) { 1329 return convertPrimitiveArrayToObjectArray(array, Byte.class); 1330 } 1331 1332 /** 1333 * Get object array from int array. 1334 * 1335 * @param array Input int array to be converted 1336 * @return Integer object array converted from input int array 1337 */ toObject(int[] array)1338 public static Integer[] toObject(int[] array) { 1339 return convertPrimitiveArrayToObjectArray(array, Integer.class); 1340 } 1341 1342 /** 1343 * Get object array from float array. 1344 * 1345 * @param array Input float array to be converted 1346 * @return Float object array converted from input float array 1347 */ toObject(float[] array)1348 public static Float[] toObject(float[] array) { 1349 return convertPrimitiveArrayToObjectArray(array, Float.class); 1350 } 1351 1352 /** 1353 * Get object array from double array. 1354 * 1355 * @param array Input double array to be converted 1356 * @return Double object array converted from input double array 1357 */ toObject(double[] array)1358 public static Double[] toObject(double[] array) { 1359 return convertPrimitiveArrayToObjectArray(array, Double.class); 1360 } 1361 1362 /** 1363 * Convert a primitive input array into its object array version (e.g. from int[] to Integer[]). 1364 * 1365 * @param array Input array object 1366 * @param wrapperClass The boxed class it converts to 1367 * @return Boxed version of primitive array 1368 */ convertPrimitiveArrayToObjectArray(final Object array, final Class<T> wrapperClass)1369 private static <T> T[] convertPrimitiveArrayToObjectArray(final Object array, 1370 final Class<T> wrapperClass) { 1371 // getLength does the null check and isArray check already. 1372 int arrayLength = Array.getLength(array); 1373 if (arrayLength == 0) { 1374 throw new IllegalArgumentException("Input array shouldn't be empty"); 1375 } 1376 1377 @SuppressWarnings("unchecked") 1378 final T[] result = (T[]) Array.newInstance(wrapperClass, arrayLength); 1379 for (int i = 0; i < arrayLength; i++) { 1380 Array.set(result, i, Array.get(array, i)); 1381 } 1382 return result; 1383 } 1384 1385 /** 1386 * Validate image based on format and size. 1387 * 1388 * @param image The image to be validated. 1389 * @param width The image width. 1390 * @param height The image height. 1391 * @param format The image format. 1392 * @param filePath The debug dump file path, null if don't want to dump to 1393 * file. 1394 * @throws UnsupportedOperationException if calling with an unknown format 1395 */ validateImage(Image image, int width, int height, int format, String filePath)1396 public static void validateImage(Image image, int width, int height, int format, 1397 String filePath) { 1398 checkImage(image, width, height, format); 1399 1400 /** 1401 * TODO: validate timestamp: 1402 * 1. capture result timestamp against the image timestamp (need 1403 * consider frame drops) 1404 * 2. timestamps should be monotonically increasing for different requests 1405 */ 1406 if(VERBOSE) Log.v(TAG, "validating Image"); 1407 byte[] data = getDataFromImage(image); 1408 assertTrue("Invalid image data", data != null && data.length > 0); 1409 1410 switch (format) { 1411 case ImageFormat.JPEG: 1412 validateJpegData(data, width, height, filePath); 1413 break; 1414 case ImageFormat.YUV_420_888: 1415 case ImageFormat.YV12: 1416 validateYuvData(data, width, height, format, image.getTimestamp(), filePath); 1417 break; 1418 case ImageFormat.RAW_SENSOR: 1419 validateRaw16Data(data, width, height, format, image.getTimestamp(), filePath); 1420 break; 1421 case ImageFormat.DEPTH16: 1422 validateDepth16Data(data, width, height, format, image.getTimestamp(), filePath); 1423 break; 1424 case ImageFormat.DEPTH_POINT_CLOUD: 1425 validateDepthPointCloudData(data, width, height, format, image.getTimestamp(), filePath); 1426 break; 1427 case ImageFormat.RAW_PRIVATE: 1428 validateRawPrivateData(data, width, height, image.getTimestamp(), filePath); 1429 break; 1430 default: 1431 throw new UnsupportedOperationException("Unsupported format for validation: " 1432 + format); 1433 } 1434 } 1435 1436 /** 1437 * Provide a mock for {@link CameraDevice.StateCallback}. 1438 * 1439 * <p>Only useful because mockito can't mock {@link CameraDevice.StateCallback} which is an 1440 * abstract class.</p> 1441 * 1442 * <p> 1443 * Use this instead of other classes when needing to verify interactions, since 1444 * trying to spy on {@link BlockingStateCallback} (or others) will cause unnecessary extra 1445 * interactions which will cause false test failures. 1446 * </p> 1447 * 1448 */ 1449 public static class MockStateCallback extends CameraDevice.StateCallback { 1450 1451 @Override onOpened(CameraDevice camera)1452 public void onOpened(CameraDevice camera) { 1453 } 1454 1455 @Override onDisconnected(CameraDevice camera)1456 public void onDisconnected(CameraDevice camera) { 1457 } 1458 1459 @Override onError(CameraDevice camera, int error)1460 public void onError(CameraDevice camera, int error) { 1461 } 1462 MockStateCallback()1463 private MockStateCallback() {} 1464 1465 /** 1466 * Create a Mockito-ready mocked StateCallback. 1467 */ mock()1468 public static MockStateCallback mock() { 1469 return Mockito.spy(new MockStateCallback()); 1470 } 1471 } 1472 validateJpegData(byte[] jpegData, int width, int height, String filePath)1473 private static void validateJpegData(byte[] jpegData, int width, int height, String filePath) { 1474 BitmapFactory.Options bmpOptions = new BitmapFactory.Options(); 1475 // DecodeBound mode: only parse the frame header to get width/height. 1476 // it doesn't decode the pixel. 1477 bmpOptions.inJustDecodeBounds = true; 1478 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, bmpOptions); 1479 assertEquals(width, bmpOptions.outWidth); 1480 assertEquals(height, bmpOptions.outHeight); 1481 1482 // Pixel decoding mode: decode whole image. check if the image data 1483 // is decodable here. 1484 assertNotNull("Decoding jpeg failed", 1485 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length)); 1486 if (DEBUG && filePath != null) { 1487 String fileName = 1488 filePath + "/" + width + "x" + height + ".jpeg"; 1489 dumpFile(fileName, jpegData); 1490 } 1491 } 1492 validateYuvData(byte[] yuvData, int width, int height, int format, long ts, String filePath)1493 private static void validateYuvData(byte[] yuvData, int width, int height, int format, 1494 long ts, String filePath) { 1495 checkYuvFormat(format); 1496 if (VERBOSE) Log.v(TAG, "Validating YUV data"); 1497 int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8; 1498 assertEquals("Yuv data doesn't match", expectedSize, yuvData.length); 1499 1500 // TODO: Can add data validation for test pattern. 1501 1502 if (DEBUG && filePath != null) { 1503 String fileName = 1504 filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".yuv"; 1505 dumpFile(fileName, yuvData); 1506 } 1507 } 1508 validateRaw16Data(byte[] rawData, int width, int height, int format, long ts, String filePath)1509 private static void validateRaw16Data(byte[] rawData, int width, int height, int format, 1510 long ts, String filePath) { 1511 if (VERBOSE) Log.v(TAG, "Validating raw data"); 1512 int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8; 1513 assertEquals("Raw data doesn't match", expectedSize, rawData.length); 1514 1515 // TODO: Can add data validation for test pattern. 1516 1517 if (DEBUG && filePath != null) { 1518 String fileName = 1519 filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".raw16"; 1520 dumpFile(fileName, rawData); 1521 } 1522 1523 return; 1524 } 1525 validateRawPrivateData(byte[] rawData, int width, int height, long ts, String filePath)1526 private static void validateRawPrivateData(byte[] rawData, int width, int height, 1527 long ts, String filePath) { 1528 if (VERBOSE) Log.v(TAG, "Validating private raw data"); 1529 // Expect each RAW pixel should occupy at least one byte and no more than 2.5 bytes 1530 int expectedSizeMin = width * height; 1531 int expectedSizeMax = width * height * 5 / 2; 1532 1533 assertTrue("Opaque RAW size " + rawData.length + "out of normal bound [" + 1534 expectedSizeMin + "," + expectedSizeMax + "]", 1535 expectedSizeMin <= rawData.length && rawData.length <= expectedSizeMax); 1536 1537 if (DEBUG && filePath != null) { 1538 String fileName = 1539 filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".rawPriv"; 1540 dumpFile(fileName, rawData); 1541 } 1542 1543 return; 1544 } 1545 validateDepth16Data(byte[] depthData, int width, int height, int format, long ts, String filePath)1546 private static void validateDepth16Data(byte[] depthData, int width, int height, int format, 1547 long ts, String filePath) { 1548 1549 if (VERBOSE) Log.v(TAG, "Validating depth16 data"); 1550 int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8; 1551 assertEquals("Depth data doesn't match", expectedSize, depthData.length); 1552 1553 1554 if (DEBUG && filePath != null) { 1555 String fileName = 1556 filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".depth16"; 1557 dumpFile(fileName, depthData); 1558 } 1559 1560 return; 1561 1562 } 1563 validateDepthPointCloudData(byte[] depthData, int width, int height, int format, long ts, String filePath)1564 private static void validateDepthPointCloudData(byte[] depthData, int width, int height, int format, 1565 long ts, String filePath) { 1566 1567 if (VERBOSE) Log.v(TAG, "Validating depth point cloud data"); 1568 1569 // Can't validate size since it is variable 1570 1571 if (DEBUG && filePath != null) { 1572 String fileName = 1573 filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".depth_point_cloud"; 1574 dumpFile(fileName, depthData); 1575 } 1576 1577 return; 1578 1579 } 1580 getValueNotNull(CaptureResult result, CaptureResult.Key<T> key)1581 public static <T> T getValueNotNull(CaptureResult result, CaptureResult.Key<T> key) { 1582 if (result == null) { 1583 throw new IllegalArgumentException("Result must not be null"); 1584 } 1585 1586 T value = result.get(key); 1587 assertNotNull("Value of Key " + key.getName() + "shouldn't be null", value); 1588 return value; 1589 } 1590 getValueNotNull(CameraCharacteristics characteristics, CameraCharacteristics.Key<T> key)1591 public static <T> T getValueNotNull(CameraCharacteristics characteristics, 1592 CameraCharacteristics.Key<T> key) { 1593 if (characteristics == null) { 1594 throw new IllegalArgumentException("Camera characteristics must not be null"); 1595 } 1596 1597 T value = characteristics.get(key); 1598 assertNotNull("Value of Key " + key.getName() + "shouldn't be null", value); 1599 return value; 1600 } 1601 1602 /** 1603 * Get a crop region for a given zoom factor and center position. 1604 * <p> 1605 * The center position is normalized position in range of [0, 1.0], where 1606 * (0, 0) represents top left corner, (1.0. 1.0) represents bottom right 1607 * corner. The center position could limit the effective minimal zoom 1608 * factor, for example, if the center position is (0.75, 0.75), the 1609 * effective minimal zoom position becomes 2.0. If the requested zoom factor 1610 * is smaller than 2.0, a crop region with 2.0 zoom factor will be returned. 1611 * </p> 1612 * <p> 1613 * The aspect ratio of the crop region is maintained the same as the aspect 1614 * ratio of active array. 1615 * </p> 1616 * 1617 * @param zoomFactor The zoom factor to generate the crop region, it must be 1618 * >= 1.0 1619 * @param center The normalized zoom center point that is in the range of [0, 1]. 1620 * @param maxZoom The max zoom factor supported by this device. 1621 * @param activeArray The active array size of this device. 1622 * @return crop region for the given normalized center and zoom factor. 1623 */ getCropRegionForZoom(float zoomFactor, final PointF center, final float maxZoom, final Rect activeArray)1624 public static Rect getCropRegionForZoom(float zoomFactor, final PointF center, 1625 final float maxZoom, final Rect activeArray) { 1626 if (zoomFactor < 1.0) { 1627 throw new IllegalArgumentException("zoom factor " + zoomFactor + " should be >= 1.0"); 1628 } 1629 if (center.x > 1.0 || center.x < 0) { 1630 throw new IllegalArgumentException("center.x " + center.x 1631 + " should be in range of [0, 1.0]"); 1632 } 1633 if (center.y > 1.0 || center.y < 0) { 1634 throw new IllegalArgumentException("center.y " + center.y 1635 + " should be in range of [0, 1.0]"); 1636 } 1637 if (maxZoom < 1.0) { 1638 throw new IllegalArgumentException("max zoom factor " + maxZoom + " should be >= 1.0"); 1639 } 1640 if (activeArray == null) { 1641 throw new IllegalArgumentException("activeArray must not be null"); 1642 } 1643 1644 float minCenterLength = Math.min(Math.min(center.x, 1.0f - center.x), 1645 Math.min(center.y, 1.0f - center.y)); 1646 float minEffectiveZoom = 0.5f / minCenterLength; 1647 if (minEffectiveZoom > maxZoom) { 1648 throw new IllegalArgumentException("Requested center " + center.toString() + 1649 " has minimal zoomable factor " + minEffectiveZoom + ", which exceeds max" 1650 + " zoom factor " + maxZoom); 1651 } 1652 1653 if (zoomFactor < minEffectiveZoom) { 1654 Log.w(TAG, "Requested zoomFactor " + zoomFactor + " < minimal zoomable factor " 1655 + minEffectiveZoom + ". It will be overwritten by " + minEffectiveZoom); 1656 zoomFactor = minEffectiveZoom; 1657 } 1658 1659 int cropCenterX = (int)(activeArray.width() * center.x); 1660 int cropCenterY = (int)(activeArray.height() * center.y); 1661 int cropWidth = (int) (activeArray.width() / zoomFactor); 1662 int cropHeight = (int) (activeArray.height() / zoomFactor); 1663 1664 return new Rect( 1665 /*left*/cropCenterX - cropWidth / 2, 1666 /*top*/cropCenterY - cropHeight / 2, 1667 /*right*/ cropCenterX + cropWidth / 2 - 1, 1668 /*bottom*/cropCenterY + cropHeight / 2 - 1); 1669 } 1670 1671 /** 1672 * Get AeAvailableTargetFpsRanges and sort them in descending order by max fps 1673 * 1674 * @param staticInfo camera static metadata 1675 * @return AeAvailableTargetFpsRanges in descending order by max fps 1676 */ getDescendingTargetFpsRanges(StaticMetadata staticInfo)1677 public static Range<Integer>[] getDescendingTargetFpsRanges(StaticMetadata staticInfo) { 1678 Range<Integer>[] fpsRanges = staticInfo.getAeAvailableTargetFpsRangesChecked(); 1679 Arrays.sort(fpsRanges, new Comparator<Range<Integer>>() { 1680 public int compare(Range<Integer> r1, Range<Integer> r2) { 1681 return r2.getUpper() - r1.getUpper(); 1682 } 1683 }); 1684 return fpsRanges; 1685 } 1686 1687 /** 1688 * Calculate output 3A region from the intersection of input 3A region and cropped region. 1689 * 1690 * @param requestRegions The input 3A regions 1691 * @param cropRect The cropped region 1692 * @return expected 3A regions output in capture result 1693 */ getExpectedOutputRegion( MeteringRectangle[] requestRegions, Rect cropRect)1694 public static MeteringRectangle[] getExpectedOutputRegion( 1695 MeteringRectangle[] requestRegions, Rect cropRect){ 1696 MeteringRectangle[] resultRegions = new MeteringRectangle[requestRegions.length]; 1697 for (int i = 0; i < requestRegions.length; i++) { 1698 Rect requestRect = requestRegions[i].getRect(); 1699 Rect resultRect = new Rect(); 1700 assertTrue("Input 3A region must intersect cropped region", 1701 resultRect.setIntersect(requestRect, cropRect)); 1702 resultRegions[i] = new MeteringRectangle( 1703 resultRect, 1704 requestRegions[i].getMeteringWeight()); 1705 } 1706 return resultRegions; 1707 } 1708 1709 /** 1710 * Copy source image data to destination image. 1711 * 1712 * @param src The source image to be copied from. 1713 * @param dst The destination image to be copied to. 1714 * @throws IllegalArgumentException If the source and destination images have 1715 * different format, or one of the images is not copyable. 1716 */ imageCopy(Image src, Image dst)1717 public static void imageCopy(Image src, Image dst) { 1718 if (src == null || dst == null) { 1719 throw new IllegalArgumentException("Images should be non-null"); 1720 } 1721 if (src.getFormat() != dst.getFormat()) { 1722 throw new IllegalArgumentException("Src and dst images should have the same format"); 1723 } 1724 if (src.getFormat() == ImageFormat.PRIVATE || 1725 dst.getFormat() == ImageFormat.PRIVATE) { 1726 throw new IllegalArgumentException("PRIVATE format images are not copyable"); 1727 } 1728 1729 // TODO: check the owner of the dst image, it must be from ImageWriter, other source may 1730 // not be writable. Maybe we should add an isWritable() method in image class. 1731 1732 Plane[] srcPlanes = src.getPlanes(); 1733 Plane[] dstPlanes = dst.getPlanes(); 1734 ByteBuffer srcBuffer = null; 1735 ByteBuffer dstBuffer = null; 1736 for (int i = 0; i < srcPlanes.length; i++) { 1737 srcBuffer = srcPlanes[i].getBuffer(); 1738 int srcPos = srcBuffer.position(); 1739 srcBuffer.rewind(); 1740 dstBuffer = dstPlanes[i].getBuffer(); 1741 dstBuffer.rewind(); 1742 dstBuffer.put(srcBuffer); 1743 srcBuffer.position(srcPos); 1744 dstBuffer.rewind(); 1745 } 1746 } 1747 1748 /** 1749 * <p> 1750 * Checks whether the two images are strongly equal. 1751 * </p> 1752 * <p> 1753 * Two images are strongly equal if and only if the data, formats, sizes, 1754 * and timestamps are same. For {@link ImageFormat#PRIVATE PRIVATE} format 1755 * images, the image data is not not accessible thus the data comparison is 1756 * effectively skipped as the number of planes is zero. 1757 * </p> 1758 * <p> 1759 * Note that this method compares the pixel data even outside of the crop 1760 * region, which may not be necessary for general use case. 1761 * </p> 1762 * 1763 * @param lhsImg First image to be compared with. 1764 * @param rhsImg Second image to be compared with. 1765 * @return true if the two images are equal, false otherwise. 1766 * @throws IllegalArgumentException If either of image is null. 1767 */ isImageStronglyEqual(Image lhsImg, Image rhsImg)1768 public static boolean isImageStronglyEqual(Image lhsImg, Image rhsImg) { 1769 if (lhsImg == null || rhsImg == null) { 1770 throw new IllegalArgumentException("Images should be non-null"); 1771 } 1772 1773 if (lhsImg.getFormat() != rhsImg.getFormat()) { 1774 Log.i(TAG, "lhsImg format " + lhsImg.getFormat() + " is different with rhsImg format " 1775 + rhsImg.getFormat()); 1776 return false; 1777 } 1778 1779 if (lhsImg.getWidth() != rhsImg.getWidth()) { 1780 Log.i(TAG, "lhsImg width " + lhsImg.getWidth() + " is different with rhsImg width " 1781 + rhsImg.getWidth()); 1782 return false; 1783 } 1784 1785 if (lhsImg.getHeight() != rhsImg.getHeight()) { 1786 Log.i(TAG, "lhsImg height " + lhsImg.getHeight() + " is different with rhsImg height " 1787 + rhsImg.getHeight()); 1788 return false; 1789 } 1790 1791 if (lhsImg.getTimestamp() != rhsImg.getTimestamp()) { 1792 Log.i(TAG, "lhsImg timestamp " + lhsImg.getTimestamp() 1793 + " is different with rhsImg timestamp " + rhsImg.getTimestamp()); 1794 return false; 1795 } 1796 1797 if (!lhsImg.getCropRect().equals(rhsImg.getCropRect())) { 1798 Log.i(TAG, "lhsImg crop rect " + lhsImg.getCropRect() 1799 + " is different with rhsImg crop rect " + rhsImg.getCropRect()); 1800 return false; 1801 } 1802 1803 // Compare data inside of the image. 1804 Plane[] lhsPlanes = lhsImg.getPlanes(); 1805 Plane[] rhsPlanes = rhsImg.getPlanes(); 1806 ByteBuffer lhsBuffer = null; 1807 ByteBuffer rhsBuffer = null; 1808 for (int i = 0; i < lhsPlanes.length; i++) { 1809 lhsBuffer = lhsPlanes[i].getBuffer(); 1810 rhsBuffer = rhsPlanes[i].getBuffer(); 1811 if (!lhsBuffer.equals(rhsBuffer)) { 1812 Log.i(TAG, "byte buffers for plane " + i + " don't matach."); 1813 return false; 1814 } 1815 } 1816 1817 return true; 1818 } 1819 1820 /** 1821 * Set jpeg related keys in a capture request builder. 1822 * 1823 * @param builder The capture request builder to set the keys inl 1824 * @param exifData The exif data to set. 1825 * @param thumbnailSize The thumbnail size to set. 1826 * @param collector The camera error collector to collect errors. 1827 */ setJpegKeys(CaptureRequest.Builder builder, ExifTestData exifData, Size thumbnailSize, CameraErrorCollector collector)1828 public static void setJpegKeys(CaptureRequest.Builder builder, ExifTestData exifData, 1829 Size thumbnailSize, CameraErrorCollector collector) { 1830 builder.set(CaptureRequest.JPEG_THUMBNAIL_SIZE, thumbnailSize); 1831 builder.set(CaptureRequest.JPEG_GPS_LOCATION, exifData.gpsLocation); 1832 builder.set(CaptureRequest.JPEG_ORIENTATION, exifData.jpegOrientation); 1833 builder.set(CaptureRequest.JPEG_QUALITY, exifData.jpegQuality); 1834 builder.set(CaptureRequest.JPEG_THUMBNAIL_QUALITY, 1835 exifData.thumbnailQuality); 1836 1837 // Validate request set and get. 1838 collector.expectEquals("JPEG thumbnail size request set and get should match", 1839 thumbnailSize, builder.get(CaptureRequest.JPEG_THUMBNAIL_SIZE)); 1840 collector.expectTrue("GPS locations request set and get should match.", 1841 areGpsFieldsEqual(exifData.gpsLocation, 1842 builder.get(CaptureRequest.JPEG_GPS_LOCATION))); 1843 collector.expectEquals("JPEG orientation request set and get should match", 1844 exifData.jpegOrientation, 1845 builder.get(CaptureRequest.JPEG_ORIENTATION)); 1846 collector.expectEquals("JPEG quality request set and get should match", 1847 exifData.jpegQuality, builder.get(CaptureRequest.JPEG_QUALITY)); 1848 collector.expectEquals("JPEG thumbnail quality request set and get should match", 1849 exifData.thumbnailQuality, 1850 builder.get(CaptureRequest.JPEG_THUMBNAIL_QUALITY)); 1851 } 1852 1853 /** 1854 * Simple validation of JPEG image size and format. 1855 * <p> 1856 * Only validate the image object sanity. It is fast, but doesn't actually 1857 * check the buffer data. Assert is used here as it make no sense to 1858 * continue the test if the jpeg image captured has some serious failures. 1859 * </p> 1860 * 1861 * @param image The captured jpeg image 1862 * @param expectedSize Expected capture jpeg size 1863 */ basicValidateJpegImage(Image image, Size expectedSize)1864 public static void basicValidateJpegImage(Image image, Size expectedSize) { 1865 Size imageSz = new Size(image.getWidth(), image.getHeight()); 1866 assertTrue( 1867 String.format("Image size doesn't match (expected %s, actual %s) ", 1868 expectedSize.toString(), imageSz.toString()), expectedSize.equals(imageSz)); 1869 assertEquals("Image format should be JPEG", ImageFormat.JPEG, image.getFormat()); 1870 assertNotNull("Image plane shouldn't be null", image.getPlanes()); 1871 assertEquals("Image plane number should be 1", 1, image.getPlanes().length); 1872 1873 // Jpeg decoding validate was done in ImageReaderTest, no need to duplicate the test here. 1874 } 1875 1876 /** 1877 * Verify the JPEG EXIF and JPEG related keys in a capture result are expected. 1878 * - Capture request get values are same as were set. 1879 * - capture result's exif data is the same as was set by 1880 * the capture request. 1881 * - new tags in the result set by the camera service are 1882 * present and semantically correct. 1883 * 1884 * @param image The output JPEG image to verify. 1885 * @param captureResult The capture result to verify. 1886 * @param expectedSize The expected JPEG size. 1887 * @param expectedThumbnailSize The expected thumbnail size. 1888 * @param expectedExifData The expected EXIF data 1889 * @param staticInfo The static metadata for the camera device. 1890 * @param jpegFilename The filename to dump the jpeg to. 1891 * @param collector The camera error collector to collect errors. 1892 */ verifyJpegKeys(Image image, CaptureResult captureResult, Size expectedSize, Size expectedThumbnailSize, ExifTestData expectedExifData, StaticMetadata staticInfo, CameraErrorCollector collector)1893 public static void verifyJpegKeys(Image image, CaptureResult captureResult, Size expectedSize, 1894 Size expectedThumbnailSize, ExifTestData expectedExifData, StaticMetadata staticInfo, 1895 CameraErrorCollector collector) throws Exception { 1896 1897 basicValidateJpegImage(image, expectedSize); 1898 1899 byte[] jpegBuffer = getDataFromImage(image); 1900 // Have to dump into a file to be able to use ExifInterface 1901 String jpegFilename = DEBUG_FILE_NAME_BASE + "/verifyJpegKeys.jpeg"; 1902 dumpFile(jpegFilename, jpegBuffer); 1903 ExifInterface exif = new ExifInterface(jpegFilename); 1904 1905 if (expectedThumbnailSize.equals(new Size(0,0))) { 1906 collector.expectTrue("Jpeg shouldn't have thumbnail when thumbnail size is (0, 0)", 1907 !exif.hasThumbnail()); 1908 } else { 1909 collector.expectTrue("Jpeg must have thumbnail for thumbnail size " + 1910 expectedThumbnailSize, exif.hasThumbnail()); 1911 } 1912 1913 // Validate capture result vs. request 1914 Size resultThumbnailSize = captureResult.get(CaptureResult.JPEG_THUMBNAIL_SIZE); 1915 int orientationTested = expectedExifData.jpegOrientation; 1916 // Legacy shim always doesn't rotate thumbnail size 1917 if ((orientationTested == 90 || orientationTested == 270) && 1918 staticInfo.isHardwareLevelAtLeastLimited()) { 1919 int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1920 /*defaultValue*/-1); 1921 if (exifOrientation == ExifInterface.ORIENTATION_UNDEFINED) { 1922 // Device physically rotated image+thumbnail data 1923 // Expect thumbnail size to be also rotated 1924 resultThumbnailSize = new Size(resultThumbnailSize.getHeight(), 1925 resultThumbnailSize.getWidth()); 1926 } 1927 } 1928 1929 collector.expectEquals("JPEG thumbnail size result and request should match", 1930 expectedThumbnailSize, resultThumbnailSize); 1931 if (collector.expectKeyValueNotNull(captureResult, CaptureResult.JPEG_GPS_LOCATION) != 1932 null) { 1933 collector.expectTrue("GPS location result and request should match.", 1934 areGpsFieldsEqual(expectedExifData.gpsLocation, 1935 captureResult.get(CaptureResult.JPEG_GPS_LOCATION))); 1936 } 1937 collector.expectEquals("JPEG orientation result and request should match", 1938 expectedExifData.jpegOrientation, 1939 captureResult.get(CaptureResult.JPEG_ORIENTATION)); 1940 collector.expectEquals("JPEG quality result and request should match", 1941 expectedExifData.jpegQuality, captureResult.get(CaptureResult.JPEG_QUALITY)); 1942 collector.expectEquals("JPEG thumbnail quality result and request should match", 1943 expectedExifData.thumbnailQuality, 1944 captureResult.get(CaptureResult.JPEG_THUMBNAIL_QUALITY)); 1945 1946 // Validate other exif tags for all non-legacy devices 1947 if (!staticInfo.isHardwareLevelLegacy()) { 1948 verifyJpegExifExtraTags(exif, expectedSize, captureResult, staticInfo, collector); 1949 } 1950 } 1951 1952 /** 1953 * Get the degree of an EXIF orientation. 1954 */ getExifOrientationInDegree(int exifOrientation, CameraErrorCollector collector)1955 private static int getExifOrientationInDegree(int exifOrientation, 1956 CameraErrorCollector collector) { 1957 switch (exifOrientation) { 1958 case ExifInterface.ORIENTATION_NORMAL: 1959 return 0; 1960 case ExifInterface.ORIENTATION_ROTATE_90: 1961 return 90; 1962 case ExifInterface.ORIENTATION_ROTATE_180: 1963 return 180; 1964 case ExifInterface.ORIENTATION_ROTATE_270: 1965 return 270; 1966 default: 1967 collector.addMessage("It is impossible to get non 0, 90, 180, 270 degress exif" + 1968 "info based on the request orientation range"); 1969 return 0; 1970 } 1971 } 1972 1973 /** 1974 * Validate and return the focal length. 1975 * 1976 * @param result Capture result to get the focal length 1977 * @return Focal length from capture result or -1 if focal length is not available. 1978 */ validateFocalLength(CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector)1979 private static float validateFocalLength(CaptureResult result, StaticMetadata staticInfo, 1980 CameraErrorCollector collector) { 1981 float[] focalLengths = staticInfo.getAvailableFocalLengthsChecked(); 1982 Float resultFocalLength = result.get(CaptureResult.LENS_FOCAL_LENGTH); 1983 if (collector.expectTrue("Focal length is invalid", 1984 resultFocalLength != null && resultFocalLength > 0)) { 1985 List<Float> focalLengthList = 1986 Arrays.asList(CameraTestUtils.toObject(focalLengths)); 1987 collector.expectTrue("Focal length should be one of the available focal length", 1988 focalLengthList.contains(resultFocalLength)); 1989 return resultFocalLength; 1990 } 1991 return -1; 1992 } 1993 1994 /** 1995 * Validate and return the aperture. 1996 * 1997 * @param result Capture result to get the aperture 1998 * @return Aperture from capture result or -1 if aperture is not available. 1999 */ validateAperture(CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector)2000 private static float validateAperture(CaptureResult result, StaticMetadata staticInfo, 2001 CameraErrorCollector collector) { 2002 float[] apertures = staticInfo.getAvailableAperturesChecked(); 2003 Float resultAperture = result.get(CaptureResult.LENS_APERTURE); 2004 if (collector.expectTrue("Capture result aperture is invalid", 2005 resultAperture != null && resultAperture > 0)) { 2006 List<Float> apertureList = 2007 Arrays.asList(CameraTestUtils.toObject(apertures)); 2008 collector.expectTrue("Aperture should be one of the available apertures", 2009 apertureList.contains(resultAperture)); 2010 return resultAperture; 2011 } 2012 return -1; 2013 } 2014 2015 /** 2016 * Return the closest value in an array of floats. 2017 */ getClosestValueInArray(float[] values, float target)2018 private static float getClosestValueInArray(float[] values, float target) { 2019 int minIdx = 0; 2020 float minDistance = Math.abs(values[0] - target); 2021 for(int i = 0; i < values.length; i++) { 2022 float distance = Math.abs(values[i] - target); 2023 if (minDistance > distance) { 2024 minDistance = distance; 2025 minIdx = i; 2026 } 2027 } 2028 2029 return values[minIdx]; 2030 } 2031 2032 /** 2033 * Return if two Location's GPS field are the same. 2034 */ areGpsFieldsEqual(Location a, Location b)2035 private static boolean areGpsFieldsEqual(Location a, Location b) { 2036 if (a == null || b == null) { 2037 return false; 2038 } 2039 2040 return a.getTime() == b.getTime() && a.getLatitude() == b.getLatitude() && 2041 a.getLongitude() == b.getLongitude() && a.getAltitude() == b.getAltitude() && 2042 a.getProvider() == b.getProvider(); 2043 } 2044 2045 /** 2046 * Verify extra tags in JPEG EXIF 2047 */ verifyJpegExifExtraTags(ExifInterface exif, Size jpegSize, CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector)2048 private static void verifyJpegExifExtraTags(ExifInterface exif, Size jpegSize, 2049 CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector) 2050 throws ParseException { 2051 /** 2052 * TAG_IMAGE_WIDTH and TAG_IMAGE_LENGTH and TAG_ORIENTATION. 2053 * Orientation and exif width/height need to be tested carefully, two cases: 2054 * 2055 * 1. Device rotate the image buffer physically, then exif width/height may not match 2056 * the requested still capture size, we need swap them to check. 2057 * 2058 * 2. Device use the exif tag to record the image orientation, it doesn't rotate 2059 * the jpeg image buffer itself. In this case, the exif width/height should always match 2060 * the requested still capture size, and the exif orientation should always match the 2061 * requested orientation. 2062 * 2063 */ 2064 int exifWidth = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, /*defaultValue*/0); 2065 int exifHeight = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, /*defaultValue*/0); 2066 Size exifSize = new Size(exifWidth, exifHeight); 2067 // Orientation could be missing, which is ok, default to 0. 2068 int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 2069 /*defaultValue*/-1); 2070 // Get requested orientation from result, because they should be same. 2071 if (collector.expectKeyValueNotNull(result, CaptureResult.JPEG_ORIENTATION) != null) { 2072 int requestedOrientation = result.get(CaptureResult.JPEG_ORIENTATION); 2073 final int ORIENTATION_MIN = ExifInterface.ORIENTATION_UNDEFINED; 2074 final int ORIENTATION_MAX = ExifInterface.ORIENTATION_ROTATE_270; 2075 boolean orientationValid = collector.expectTrue(String.format( 2076 "Exif orientation must be in range of [%d, %d]", 2077 ORIENTATION_MIN, ORIENTATION_MAX), 2078 exifOrientation >= ORIENTATION_MIN && exifOrientation <= ORIENTATION_MAX); 2079 if (orientationValid) { 2080 /** 2081 * Device captured image doesn't respect the requested orientation, 2082 * which means it rotates the image buffer physically. Then we 2083 * should swap the exif width/height accordingly to compare. 2084 */ 2085 boolean deviceRotatedImage = exifOrientation == ExifInterface.ORIENTATION_UNDEFINED; 2086 2087 if (deviceRotatedImage) { 2088 // Case 1. 2089 boolean needSwap = (requestedOrientation % 180 == 90); 2090 if (needSwap) { 2091 exifSize = new Size(exifHeight, exifWidth); 2092 } 2093 } else { 2094 // Case 2. 2095 collector.expectEquals("Exif orientaiton should match requested orientation", 2096 requestedOrientation, getExifOrientationInDegree(exifOrientation, 2097 collector)); 2098 } 2099 } 2100 } 2101 2102 /** 2103 * Ideally, need check exifSize == jpegSize == actual buffer size. But 2104 * jpegSize == jpeg decode bounds size(from jpeg jpeg frame 2105 * header, not exif) was validated in ImageReaderTest, no need to 2106 * validate again here. 2107 */ 2108 collector.expectEquals("Exif size should match jpeg capture size", jpegSize, exifSize); 2109 2110 // TAG_DATETIME, it should be local time 2111 long currentTimeInMs = System.currentTimeMillis(); 2112 long currentTimeInSecond = currentTimeInMs / 1000; 2113 Date date = new Date(currentTimeInMs); 2114 String localDatetime = new SimpleDateFormat("yyyy:MM:dd HH:").format(date); 2115 String dateTime = exif.getAttribute(ExifInterface.TAG_DATETIME); 2116 if (collector.expectTrue("Exif TAG_DATETIME shouldn't be null", dateTime != null)) { 2117 collector.expectTrue("Exif TAG_DATETIME is wrong", 2118 dateTime.length() == EXIF_DATETIME_LENGTH); 2119 long exifTimeInSecond = 2120 new SimpleDateFormat("yyyy:MM:dd HH:mm:ss").parse(dateTime).getTime() / 1000; 2121 long delta = currentTimeInSecond - exifTimeInSecond; 2122 collector.expectTrue("Capture time deviates too much from the current time", 2123 Math.abs(delta) < EXIF_DATETIME_ERROR_MARGIN_SEC); 2124 // It should be local time. 2125 collector.expectTrue("Exif date time should be local time", 2126 dateTime.startsWith(localDatetime)); 2127 } 2128 2129 // TAG_FOCAL_LENGTH. 2130 float[] focalLengths = staticInfo.getAvailableFocalLengthsChecked(); 2131 float exifFocalLength = (float)exif.getAttributeDouble(ExifInterface.TAG_FOCAL_LENGTH, -1); 2132 collector.expectEquals("Focal length should match", 2133 getClosestValueInArray(focalLengths, exifFocalLength), 2134 exifFocalLength, EXIF_FOCAL_LENGTH_ERROR_MARGIN); 2135 // More checks for focal length. 2136 collector.expectEquals("Exif focal length should match capture result", 2137 validateFocalLength(result, staticInfo, collector), 2138 exifFocalLength, EXIF_FOCAL_LENGTH_ERROR_MARGIN); 2139 2140 // TAG_EXPOSURE_TIME 2141 // ExifInterface API gives exposure time value in the form of float instead of rational 2142 String exposureTime = exif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME); 2143 collector.expectNotNull("Exif TAG_EXPOSURE_TIME shouldn't be null", exposureTime); 2144 if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_EXPOSURE_TIME)) { 2145 if (exposureTime != null) { 2146 double exposureTimeValue = Double.parseDouble(exposureTime); 2147 long expTimeResult = result.get(CaptureResult.SENSOR_EXPOSURE_TIME); 2148 double expected = expTimeResult / 1e9; 2149 double tolerance = expected * EXIF_EXPOSURE_TIME_ERROR_MARGIN_RATIO; 2150 tolerance = Math.max(tolerance, EXIF_EXPOSURE_TIME_MIN_ERROR_MARGIN_SEC); 2151 collector.expectEquals("Exif exposure time doesn't match", expected, 2152 exposureTimeValue, tolerance); 2153 } 2154 } 2155 2156 // TAG_APERTURE 2157 // ExifInterface API gives aperture value in the form of float instead of rational 2158 String exifAperture = exif.getAttribute(ExifInterface.TAG_APERTURE); 2159 collector.expectNotNull("Exif TAG_APERTURE shouldn't be null", exifAperture); 2160 if (staticInfo.areKeysAvailable(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES)) { 2161 float[] apertures = staticInfo.getAvailableAperturesChecked(); 2162 if (exifAperture != null) { 2163 float apertureValue = Float.parseFloat(exifAperture); 2164 collector.expectEquals("Aperture value should match", 2165 getClosestValueInArray(apertures, apertureValue), 2166 apertureValue, EXIF_APERTURE_ERROR_MARGIN); 2167 // More checks for aperture. 2168 collector.expectEquals("Exif aperture length should match capture result", 2169 validateAperture(result, staticInfo, collector), 2170 apertureValue, EXIF_APERTURE_ERROR_MARGIN); 2171 } 2172 } 2173 2174 /** 2175 * TAG_FLASH. TODO: For full devices, can check a lot more info 2176 * (http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html#Flash) 2177 */ 2178 String flash = exif.getAttribute(ExifInterface.TAG_FLASH); 2179 collector.expectNotNull("Exif TAG_FLASH shouldn't be null", flash); 2180 2181 /** 2182 * TAG_WHITE_BALANCE. TODO: For full devices, with the DNG tags, we 2183 * should be able to cross-check android.sensor.referenceIlluminant. 2184 */ 2185 String whiteBalance = exif.getAttribute(ExifInterface.TAG_WHITE_BALANCE); 2186 collector.expectNotNull("Exif TAG_WHITE_BALANCE shouldn't be null", whiteBalance); 2187 2188 // TAG_MAKE 2189 String make = exif.getAttribute(ExifInterface.TAG_MAKE); 2190 collector.expectEquals("Exif TAG_MAKE is incorrect", Build.MANUFACTURER, make); 2191 2192 // TAG_MODEL 2193 String model = exif.getAttribute(ExifInterface.TAG_MODEL); 2194 collector.expectEquals("Exif TAG_MODEL is incorrect", Build.MODEL, model); 2195 2196 2197 // TAG_ISO 2198 int iso = exif.getAttributeInt(ExifInterface.TAG_ISO, /*defaultValue*/-1); 2199 if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_SENSITIVITY) || 2200 staticInfo.areKeysAvailable(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST)) { 2201 int expectedIso = 100; 2202 if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_SENSITIVITY)) { 2203 expectedIso = result.get(CaptureResult.SENSOR_SENSITIVITY); 2204 } 2205 if (staticInfo.areKeysAvailable(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST)) { 2206 expectedIso = expectedIso * 2207 result.get(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST) / 100; 2208 } 2209 collector.expectEquals("Exif TAG_ISO is incorrect", expectedIso, iso); 2210 } 2211 2212 // TAG_DATETIME_DIGITIZED (a.k.a Create time for digital cameras). 2213 String digitizedTime = exif.getAttribute(ExifInterface.TAG_DATETIME_DIGITIZED); 2214 collector.expectNotNull("Exif TAG_DATETIME_DIGITIZED shouldn't be null", digitizedTime); 2215 if (digitizedTime != null) { 2216 String expectedDateTime = exif.getAttribute(ExifInterface.TAG_DATETIME); 2217 collector.expectNotNull("Exif TAG_DATETIME shouldn't be null", expectedDateTime); 2218 if (expectedDateTime != null) { 2219 collector.expectEquals("dataTime should match digitizedTime", 2220 expectedDateTime, digitizedTime); 2221 } 2222 } 2223 2224 /** 2225 * TAG_SUBSEC_TIME. Since the sub second tag strings are truncated to at 2226 * most 9 digits in ExifInterface implementation, use getAttributeInt to 2227 * sanitize it. When the default value -1 is returned, it means that 2228 * this exif tag either doesn't exist or is a non-numerical invalid 2229 * string. Same rule applies to the rest of sub second tags. 2230 */ 2231 int subSecTime = exif.getAttributeInt(ExifInterface.TAG_SUBSEC_TIME, /*defaultValue*/-1); 2232 collector.expectTrue("Exif TAG_SUBSEC_TIME value is null or invalid!", subSecTime > 0); 2233 2234 // TAG_SUBSEC_TIME_ORIG 2235 int subSecTimeOrig = exif.getAttributeInt(ExifInterface.TAG_SUBSEC_TIME_ORIG, 2236 /*defaultValue*/-1); 2237 collector.expectTrue("Exif TAG_SUBSEC_TIME_ORIG value is null or invalid!", 2238 subSecTimeOrig > 0); 2239 2240 // TAG_SUBSEC_TIME_DIG 2241 int subSecTimeDig = exif.getAttributeInt(ExifInterface.TAG_SUBSEC_TIME_DIG, 2242 /*defaultValue*/-1); 2243 collector.expectTrue( 2244 "Exif TAG_SUBSEC_TIME_DIG value is null or invalid!", subSecTimeDig > 0); 2245 } 2246 2247 2248 /** 2249 * Immutable class wrapping the exif test data. 2250 */ 2251 public static class ExifTestData { 2252 public final Location gpsLocation; 2253 public final int jpegOrientation; 2254 public final byte jpegQuality; 2255 public final byte thumbnailQuality; 2256 ExifTestData(Location location, int orientation, byte jpgQuality, byte thumbQuality)2257 public ExifTestData(Location location, int orientation, 2258 byte jpgQuality, byte thumbQuality) { 2259 gpsLocation = location; 2260 jpegOrientation = orientation; 2261 jpegQuality = jpgQuality; 2262 thumbnailQuality = thumbQuality; 2263 } 2264 } 2265 getPreviewSizeBound(WindowManager windowManager, Size bound)2266 public static Size getPreviewSizeBound(WindowManager windowManager, Size bound) { 2267 Display display = windowManager.getDefaultDisplay(); 2268 2269 int width = display.getWidth(); 2270 int height = display.getHeight(); 2271 2272 if (height > width) { 2273 height = width; 2274 width = display.getHeight(); 2275 } 2276 2277 if (bound.getWidth() <= width && 2278 bound.getHeight() <= height) 2279 return bound; 2280 else 2281 return new Size(width, height); 2282 } 2283 2284 /** 2285 * Check if a particular stream configuration is supported by configuring it 2286 * to the device. 2287 */ isStreamConfigurationSupported(CameraDevice camera, List<Surface> outputSurfaces, CameraCaptureSession.StateCallback listener, Handler handler)2288 public static boolean isStreamConfigurationSupported(CameraDevice camera, 2289 List<Surface> outputSurfaces, 2290 CameraCaptureSession.StateCallback listener, Handler handler) { 2291 try { 2292 configureCameraSession(camera, outputSurfaces, listener, handler); 2293 return true; 2294 } catch (Exception e) { 2295 Log.i(TAG, "This stream configuration is not supported due to " + e.getMessage()); 2296 return false; 2297 } 2298 } 2299 } 2300