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