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