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