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