1 /* 2 * Copyright (C) 2019 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 package com.android.cts.verifier.camera.bokeh; 17 18 import com.android.cts.verifier.PassFailButtons; 19 import com.android.cts.verifier.R; 20 21 import com.android.ex.camera2.blocking.BlockingCameraManager; 22 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException; 23 import com.android.ex.camera2.blocking.BlockingStateCallback; 24 import com.android.ex.camera2.blocking.BlockingSessionCallback; 25 26 import android.app.AlertDialog; 27 import android.content.res.Configuration; 28 import android.graphics.Bitmap; 29 import android.graphics.BitmapFactory; 30 import android.graphics.Color; 31 import android.graphics.ColorFilter; 32 import android.graphics.ColorMatrixColorFilter; 33 import android.graphics.ImageFormat; 34 import android.graphics.Matrix; 35 import android.graphics.RectF; 36 import android.graphics.SurfaceTexture; 37 import android.hardware.camera2.CameraAccessException; 38 import android.hardware.camera2.CameraCaptureSession; 39 import android.hardware.camera2.CameraCharacteristics; 40 import android.hardware.camera2.CameraCharacteristics.Key; 41 import android.hardware.camera2.CameraDevice; 42 import android.hardware.camera2.CameraManager; 43 import android.hardware.camera2.CameraMetadata; 44 import android.hardware.camera2.CaptureRequest; 45 import android.hardware.camera2.CaptureResult; 46 import android.hardware.camera2.params.Capability; 47 import android.hardware.camera2.params.StreamConfigurationMap; 48 import android.hardware.camera2.TotalCaptureResult; 49 import android.media.Image; 50 import android.media.ImageReader; 51 import android.os.Bundle; 52 import android.os.Handler; 53 import android.os.HandlerThread; 54 import android.util.Log; 55 import android.util.Size; 56 import android.util.SparseArray; 57 import android.view.Menu; 58 import android.view.MenuItem; 59 import android.view.View; 60 import android.view.Surface; 61 import android.view.TextureView; 62 import android.widget.AdapterView; 63 import android.widget.ArrayAdapter; 64 import android.widget.Button; 65 import android.widget.ImageButton; 66 import android.widget.ImageView; 67 import android.widget.Spinner; 68 import android.widget.TextView; 69 import android.widget.Toast; 70 import android.content.Context; 71 72 import java.lang.Math; 73 import java.nio.ByteBuffer; 74 import java.util.ArrayList; 75 import java.util.Arrays; 76 import java.util.HashMap; 77 import java.util.Comparator; 78 import java.util.List; 79 import java.util.Optional; 80 import java.util.Set; 81 import java.util.TreeSet; 82 83 /** 84 * Tests for manual verification of bokeh modes supported by the camera device. 85 */ 86 public class CameraBokehActivity extends PassFailButtons.Activity 87 implements TextureView.SurfaceTextureListener, 88 ImageReader.OnImageAvailableListener { 89 90 private static final String TAG = "CameraBokehActivity"; 91 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 92 private static final int SESSION_READY_TIMEOUT_MS = 5000; 93 private static final Size FULLHD = new Size(1920, 1080); 94 private static final ColorMatrixColorFilter sJFIF_YUVToRGB_Filter = 95 new ColorMatrixColorFilter(new float[] { 96 1f, 0f, 1.402f, 0f, -179.456f, 97 1f, -0.34414f, -0.71414f, 0f, 135.46f, 98 1f, 1.772f, 0f, 0f, -226.816f, 99 0f, 0f, 0f, 1f, 0f 100 }); 101 102 private TextureView mPreviewView; 103 private SurfaceTexture mPreviewTexture; 104 private Surface mPreviewSurface; 105 private int mPreviewTexWidth, mPreviewTexHeight; 106 107 private ImageView mImageView; 108 private ColorFilter mCurrentColorFilter; 109 110 private Spinner mCameraSpinner; 111 private TextView mTestLabel; 112 private TextView mPreviewLabel; 113 private TextView mImageLabel; 114 115 private CameraManager mCameraManager; 116 private String[] mCameraIdList; 117 private HandlerThread mCameraThread; 118 private Handler mCameraHandler; 119 private BlockingCameraManager mBlockingCameraManager; 120 private CameraCharacteristics mCameraCharacteristics; 121 private BlockingStateCallback mCameraListener; 122 123 private BlockingSessionCallback mSessionListener; 124 private CaptureRequest.Builder mPreviewRequestBuilder; 125 private CaptureRequest mPreviewRequest; 126 private CaptureRequest.Builder mStillCaptureRequestBuilder; 127 private CaptureRequest mStillCaptureRequest; 128 129 private HashMap<String, ArrayList<Capability>> mTestCases = new HashMap<>(); 130 private int mCurrentCameraIndex = -1; 131 private String mCameraId; 132 private CameraCaptureSession mCaptureSession; 133 private CameraDevice mCameraDevice; 134 135 SizeComparator mSizeComparator = new SizeComparator(); 136 137 private Size mPreviewSize; 138 private Size mJpegSize; 139 private ImageReader mJpegImageReader; 140 private ImageReader mYuvImageReader; 141 142 private SparseArray<String> mModeNames; 143 144 private CameraCombination mNextCombination; 145 private Size mMaxBokehStreamingSize; 146 147 private Button mNextButton; 148 149 private final TreeSet<CameraCombination> mTestedCombinations = new TreeSet<>(COMPARATOR); 150 private final TreeSet<CameraCombination> mUntestedCombinations = new TreeSet<>(COMPARATOR); 151 private final TreeSet<String> mUntestedCameras = new TreeSet<>(); 152 153 // Menu to show the test progress 154 private static final int MENU_ID_PROGRESS = Menu.FIRST + 1; 155 156 private class CameraCombination { 157 private final int mCameraIndex; 158 private final int mMode; 159 private final Size mPreviewSize; 160 private final boolean mIsStillCapture; 161 private final String mCameraId; 162 private final String mModeName; 163 CameraCombination(int cameraIndex, int mode, int streamingWidth, int streamingHeight, String cameraId, String modeName, boolean isStillCapture)164 private CameraCombination(int cameraIndex, int mode, 165 int streamingWidth, int streamingHeight, 166 String cameraId, String modeName, 167 boolean isStillCapture) { 168 this.mCameraIndex = cameraIndex; 169 this.mMode = mode; 170 this.mPreviewSize = new Size(streamingWidth, streamingHeight); 171 this.mCameraId = cameraId; 172 this.mModeName = modeName; 173 this.mIsStillCapture = isStillCapture; 174 } 175 176 @Override toString()177 public String toString() { 178 return String.format("Camera %s, mode %s, intent %s", 179 mCameraId, mModeName, mIsStillCapture ? "PREVIEW" : "STILL_CAPTURE"); 180 } 181 } 182 183 private static final Comparator<CameraCombination> COMPARATOR = 184 Comparator.<CameraCombination, Integer>comparing(c -> c.mCameraIndex) 185 .thenComparing(c -> c.mMode) 186 .thenComparing(c -> c.mIsStillCapture); 187 188 private CameraCaptureSession.CaptureCallback mCaptureCallback = 189 new CameraCaptureSession.CaptureCallback() { 190 @Override 191 public void onCaptureProgressed(CameraCaptureSession session, 192 CaptureRequest request, 193 CaptureResult partialResult) { 194 // Don't need to do anything here. 195 } 196 197 @Override 198 public void onCaptureCompleted(CameraCaptureSession session, 199 CaptureRequest request, 200 TotalCaptureResult result) { 201 // Don't need to do anything here. 202 } 203 }; 204 205 @Override onCreate(Bundle savedInstanceState)206 public void onCreate(Bundle savedInstanceState) { 207 super.onCreate(savedInstanceState); 208 209 setContentView(R.layout.cb_main); 210 211 setPassFailButtonClickListeners(); 212 213 mPreviewView = (TextureView) findViewById(R.id.preview_view); 214 mImageView = (ImageView) findViewById(R.id.image_view); 215 216 mPreviewView.setSurfaceTextureListener(this); 217 218 mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); 219 try { 220 mCameraIdList = mCameraManager.getCameraIdList(); 221 for (String id : mCameraIdList) { 222 CameraCharacteristics characteristics = 223 mCameraManager.getCameraCharacteristics(id); 224 Key<Capability[]> key = 225 CameraCharacteristics.CONTROL_AVAILABLE_EXTENDED_SCENE_MODE_CAPABILITIES; 226 Capability[] extendedSceneModeCaps = characteristics.get(key); 227 228 if (extendedSceneModeCaps == null) { 229 continue; 230 } 231 232 ArrayList<Capability> nonOffModes = new ArrayList<>(); 233 for (Capability cap : extendedSceneModeCaps) { 234 int mode = cap.getMode(); 235 if (mode == CameraMetadata.CONTROL_EXTENDED_SCENE_MODE_BOKEH_STILL_CAPTURE || 236 mode == CameraMetadata.CONTROL_EXTENDED_SCENE_MODE_BOKEH_CONTINUOUS) { 237 nonOffModes.add(cap); 238 } 239 } 240 241 if (nonOffModes.size() > 0) { 242 mUntestedCameras.add("All combinations for Camera " + id); 243 mTestCases.put(id, nonOffModes); 244 } 245 246 } 247 } catch (CameraAccessException e) { 248 e.printStackTrace(); 249 } 250 251 // If no supported bokeh modes, mark the test as pass 252 if (mTestCases.size() == 0) { 253 setInfoResources(R.string.camera_bokeh_test, R.string.camera_bokeh_no_support, -1); 254 setPassButtonEnabled(true); 255 } else { 256 setInfoResources(R.string.camera_bokeh_test, R.string.camera_bokeh_test_info, -1); 257 // disable "Pass" button until all combinations are tested 258 setPassButtonEnabled(false); 259 } 260 261 Set<String> cameraIdSet = mTestCases.keySet(); 262 String[] cameraNames = new String[cameraIdSet.size()]; 263 int i = 0; 264 for (String id : cameraIdSet) { 265 cameraNames[i++] = "Camera " + id; 266 } 267 mCameraSpinner = (Spinner) findViewById(R.id.cameras_selection); 268 mCameraSpinner.setAdapter( 269 new ArrayAdapter<String>( 270 this, R.layout.camera_list_item, cameraNames)); 271 mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener); 272 273 mTestLabel = (TextView) findViewById(R.id.test_label); 274 mPreviewLabel = (TextView) findViewById(R.id.preview_label); 275 mImageLabel = (TextView) findViewById(R.id.image_label); 276 277 // Must be kept in sync with camera bokeh mode manually 278 mModeNames = new SparseArray(2); 279 mModeNames.append( 280 CameraMetadata.CONTROL_EXTENDED_SCENE_MODE_BOKEH_STILL_CAPTURE, "STILL_CAPTURE"); 281 mModeNames.append( 282 CameraMetadata.CONTROL_EXTENDED_SCENE_MODE_BOKEH_CONTINUOUS, "CONTINUOUS"); 283 284 mNextButton = findViewById(R.id.next_button); 285 mNextButton.setOnClickListener(v -> { 286 if (mNextCombination != null) { 287 mUntestedCombinations.remove(mNextCombination); 288 mTestedCombinations.add(mNextCombination); 289 } 290 setUntestedCombination(); 291 292 if (mNextCombination != null) { 293 if (mNextCombination.mIsStillCapture) { 294 takePicture(); 295 } else { 296 if (mCaptureSession != null) { 297 mCaptureSession.close(); 298 } 299 startPreview(); 300 } 301 } 302 }); 303 304 mBlockingCameraManager = new BlockingCameraManager(mCameraManager); 305 mCameraListener = new BlockingStateCallback(); 306 } 307 308 /** 309 * Set an untested combination of resolution and bokeh mode for the current camera. 310 * Triggered by next button click. 311 */ setUntestedCombination()312 private void setUntestedCombination() { 313 Optional<CameraCombination> combination = mUntestedCombinations.stream().filter( 314 c -> c.mCameraIndex == mCurrentCameraIndex).findFirst(); 315 if (!combination.isPresent()) { 316 Toast.makeText(this, "All Camera " + mCurrentCameraIndex + " tests are done.", 317 Toast.LENGTH_SHORT).show(); 318 mNextCombination = null; 319 320 if (mUntestedCombinations.isEmpty() && mUntestedCameras.isEmpty()) { 321 setPassButtonEnabled(true); 322 } 323 return; 324 } 325 326 // There is untested combination for the current camera, set the next untested combination. 327 mNextCombination = combination.get(); 328 int nextMode = mNextCombination.mMode; 329 ArrayList<Capability> bokehCaps = mTestCases.get(mCameraId); 330 for (Capability cap : bokehCaps) { 331 if (cap.getMode() == nextMode) { 332 mMaxBokehStreamingSize = cap.getMaxStreamingSize(); 333 } 334 } 335 336 // Update bokeh mode and use case 337 String testString = "Mode: " + mModeNames.get(mNextCombination.mMode); 338 if (mNextCombination.mIsStillCapture) { 339 testString += "\nIntent: Capture"; 340 } else { 341 testString += "\nIntent: Preview"; 342 } 343 testString += "\n\nPress Next if the bokeh effect works as intended"; 344 mTestLabel.setText(testString); 345 346 // Update preview view and image view bokeh expectation 347 boolean previewIsBokehCompatible = 348 mSizeComparator.compare(mNextCombination.mPreviewSize, mMaxBokehStreamingSize) <= 0; 349 String previewLabel = "Normal preview"; 350 if (previewIsBokehCompatible || mNextCombination.mIsStillCapture) { 351 previewLabel += " with bokeh"; 352 } 353 mPreviewLabel.setText(previewLabel); 354 355 String imageLabel; 356 if (mNextCombination.mIsStillCapture) { 357 imageLabel = "JPEG with bokeh"; 358 } else { 359 imageLabel = "YUV"; 360 if (previewIsBokehCompatible) { 361 imageLabel += " with bokeh"; 362 } 363 } 364 mImageLabel.setText(imageLabel); 365 } 366 367 @Override onCreateOptionsMenu(Menu menu)368 public boolean onCreateOptionsMenu(Menu menu) { 369 menu.add(Menu.NONE, MENU_ID_PROGRESS, Menu.NONE, "Current Progress"); 370 return super.onCreateOptionsMenu(menu); 371 } 372 373 @Override onOptionsItemSelected(MenuItem item)374 public boolean onOptionsItemSelected(MenuItem item) { 375 boolean ret = true; 376 switch (item.getItemId()) { 377 case MENU_ID_PROGRESS: 378 showCombinationsDialog(); 379 ret = true; 380 break; 381 default: 382 ret = super.onOptionsItemSelected(item); 383 break; 384 } 385 return ret; 386 } 387 showCombinationsDialog()388 private void showCombinationsDialog() { 389 AlertDialog.Builder builder = 390 new AlertDialog.Builder(CameraBokehActivity.this); 391 builder.setMessage(getTestDetails()) 392 .setTitle("Current Progress") 393 .setPositiveButton("OK", null); 394 builder.show(); 395 } 396 397 @Override onResume()398 public void onResume() { 399 super.onResume(); 400 401 startBackgroundThread(); 402 403 int cameraIndex = mCameraSpinner.getSelectedItemPosition(); 404 if (cameraIndex >= 0) { 405 setUpCamera(mCameraSpinner.getSelectedItemPosition()); 406 } 407 } 408 409 @Override onPause()410 public void onPause() { 411 shutdownCamera(); 412 stopBackgroundThread(); 413 414 super.onPause(); 415 } 416 417 @Override getTestDetails()418 public String getTestDetails() { 419 StringBuilder reportBuilder = new StringBuilder(); 420 reportBuilder.append("Tested combinations:\n"); 421 for (CameraCombination combination: mTestedCombinations) { 422 reportBuilder.append(combination); 423 reportBuilder.append("\n"); 424 } 425 426 reportBuilder.append("Untested cameras:\n"); 427 for (String untestedCamera : mUntestedCameras) { 428 reportBuilder.append(untestedCamera); 429 reportBuilder.append("\n"); 430 } 431 reportBuilder.append("Untested combinations:\n"); 432 for (CameraCombination combination: mUntestedCombinations) { 433 reportBuilder.append(combination); 434 reportBuilder.append("\n"); 435 } 436 return reportBuilder.toString(); 437 } 438 439 @Override onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height)440 public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, 441 int width, int height) { 442 mPreviewTexture = surfaceTexture; 443 mPreviewTexWidth = width; 444 mPreviewTexHeight = height; 445 446 mPreviewSurface = new Surface(mPreviewTexture); 447 448 if (mCameraDevice != null) { 449 startPreview(); 450 } 451 } 452 453 @Override onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)454 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 455 // Ignored, Camera does all the work for us 456 if (VERBOSE) { 457 Log.v(TAG, "onSurfaceTextureSizeChanged: " + width + " x " + height); 458 } 459 } 460 461 @Override onSurfaceTextureDestroyed(SurfaceTexture surface)462 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 463 mPreviewTexture = null; 464 return true; 465 } 466 467 @Override onSurfaceTextureUpdated(SurfaceTexture surface)468 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 469 // Invoked every time there's a new Camera preview frame 470 } 471 472 @Override onImageAvailable(ImageReader reader)473 public void onImageAvailable(ImageReader reader) { 474 Image img = null; 475 try { 476 img = reader.acquireNextImage(); 477 if (img == null) { 478 Log.d(TAG, "Invalid image!"); 479 return; 480 } 481 final int format = img.getFormat(); 482 483 Size configuredSize = (format == ImageFormat.YUV_420_888 ? mPreviewSize : mJpegSize); 484 Bitmap imgBitmap = null; 485 if (format == ImageFormat.YUV_420_888) { 486 ByteBuffer yBuffer = img.getPlanes()[0].getBuffer(); 487 ByteBuffer uBuffer = img.getPlanes()[1].getBuffer(); 488 ByteBuffer vBuffer = img.getPlanes()[2].getBuffer(); 489 yBuffer.rewind(); 490 uBuffer.rewind(); 491 vBuffer.rewind(); 492 int w = configuredSize.getWidth(); 493 int h = configuredSize.getHeight(); 494 int stride = img.getPlanes()[0].getRowStride(); 495 int uStride = img.getPlanes()[1].getRowStride(); 496 int vStride = img.getPlanes()[2].getRowStride(); 497 int uPStride = img.getPlanes()[1].getPixelStride(); 498 int vPStride = img.getPlanes()[2].getPixelStride(); 499 byte[] row = new byte[configuredSize.getWidth()]; 500 byte[] uRow = new byte[(configuredSize.getWidth()/2-1)*uPStride + 1]; 501 byte[] vRow = new byte[(configuredSize.getWidth()/2-1)*vPStride + 1]; 502 int[] imgArray = new int[w * h]; 503 for (int y = 0, j = 0, rowStart = 0, uRowStart = 0, vRowStart = 0; y < h; 504 y++, rowStart += stride) { 505 yBuffer.position(rowStart); 506 yBuffer.get(row); 507 if (y % 2 == 0) { 508 uBuffer.position(uRowStart); 509 uBuffer.get(uRow); 510 vBuffer.position(vRowStart); 511 vBuffer.get(vRow); 512 uRowStart += uStride; 513 vRowStart += vStride; 514 } 515 for (int x = 0, i = 0; x < w; x++) { 516 int yval = row[i] & 0xFF; 517 int uval = uRow[i/2 * uPStride] & 0xFF; 518 int vval = vRow[i/2 * vPStride] & 0xFF; 519 // Write YUV directly; the ImageView color filter will convert to RGB for us. 520 imgArray[j] = Color.rgb(yval, uval, vval); 521 i++; 522 j++; 523 } 524 } 525 img.close(); 526 imgBitmap = Bitmap.createBitmap(imgArray, w, h, Bitmap.Config.ARGB_8888); 527 } else if (format == ImageFormat.JPEG) { 528 ByteBuffer jpegBuffer = img.getPlanes()[0].getBuffer(); 529 jpegBuffer.rewind(); 530 byte[] jpegData = new byte[jpegBuffer.limit()]; 531 jpegBuffer.get(jpegData); 532 imgBitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); 533 img.close(); 534 } else { 535 Log.i(TAG, "Unsupported image format: " + format); 536 } 537 if (imgBitmap != null) { 538 final Bitmap bitmap = imgBitmap; 539 runOnUiThread(new Runnable() { 540 @Override 541 public void run() { 542 if (format == ImageFormat.YUV_420_888 && (mCurrentColorFilter == null || 543 !mCurrentColorFilter.equals(sJFIF_YUVToRGB_Filter))) { 544 mCurrentColorFilter = sJFIF_YUVToRGB_Filter; 545 mImageView.setColorFilter(mCurrentColorFilter); 546 } else if (format == ImageFormat.JPEG && mCurrentColorFilter != null && 547 mCurrentColorFilter.equals(sJFIF_YUVToRGB_Filter)) { 548 mCurrentColorFilter = null; 549 mImageView.clearColorFilter(); 550 } 551 mImageView.setImageBitmap(bitmap); 552 } 553 }); 554 } 555 } catch (java.lang.IllegalStateException e) { 556 // Swallow exceptions 557 e.printStackTrace(); 558 } finally { 559 if (img != null) { 560 img.close(); 561 } 562 } 563 } 564 565 private AdapterView.OnItemSelectedListener mCameraSpinnerListener = 566 new AdapterView.OnItemSelectedListener() { 567 public void onItemSelected(AdapterView<?> parent, 568 View view, int pos, long id) { 569 if (mCurrentCameraIndex != pos) { 570 setUpCamera(pos); 571 } 572 } 573 574 public void onNothingSelected(AdapterView parent) { 575 } 576 }; 577 578 private class SizeComparator implements Comparator<Size> { 579 @Override compare(Size lhs, Size rhs)580 public int compare(Size lhs, Size rhs) { 581 long lha = lhs.getWidth() * lhs.getHeight(); 582 long rha = rhs.getWidth() * rhs.getHeight(); 583 if (lha == rha) { 584 lha = lhs.getWidth(); 585 rha = rhs.getWidth(); 586 } 587 return (lha < rha) ? -1 : (lha > rha ? 1 : 0); 588 } 589 } 590 setUpCamera(int index)591 private void setUpCamera(int index) { 592 shutdownCamera(); 593 594 mCurrentCameraIndex = index; 595 mCameraId = mCameraIdList[index]; 596 try { 597 mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId); 598 mCameraDevice = mBlockingCameraManager.openCamera(mCameraId, 599 mCameraListener, mCameraHandler); 600 } catch (CameraAccessException e) { 601 e.printStackTrace(); 602 } catch (BlockingOpenException e) { 603 e.printStackTrace(); 604 } 605 606 // Update untested cameras 607 mUntestedCameras.remove("All combinations for Camera " + mCameraId); 608 609 StreamConfigurationMap config = 610 mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 611 Size[] jpegSizes = config.getOutputSizes(ImageFormat.JPEG); 612 Arrays.sort(jpegSizes, mSizeComparator); 613 mJpegSize = jpegSizes[jpegSizes.length-1]; 614 615 Size[] yuvSizes = config.getOutputSizes(ImageFormat.YUV_420_888); 616 Arrays.sort(yuvSizes, mSizeComparator); 617 Size maxYuvSize = yuvSizes[yuvSizes.length-1]; 618 if (mSizeComparator.compare(maxYuvSize, FULLHD) > 1) { 619 maxYuvSize = FULLHD; 620 } 621 622 // Update untested entries 623 ArrayList<Capability> currentTestCase = mTestCases.get(mCameraId); 624 for (Capability bokehCap : currentTestCase) { 625 Size maxStreamingSize = bokehCap.getMaxStreamingSize(); 626 Size previewSize; 627 if ((maxStreamingSize.getWidth() == 0 && maxStreamingSize.getHeight() == 0) || 628 (mSizeComparator.compare(maxStreamingSize, maxYuvSize) > 0)) { 629 previewSize = maxYuvSize; 630 } else { 631 previewSize = maxStreamingSize; 632 } 633 634 CameraCombination combination = new CameraCombination( 635 index, bokehCap.getMode(), previewSize.getWidth(), 636 previewSize.getHeight(), mCameraId, 637 mModeNames.get(bokehCap.getMode()), 638 /*isStillCapture*/false); 639 640 if (!mTestedCombinations.contains(combination)) { 641 mUntestedCombinations.add(combination); 642 } 643 644 // For BOKEH_MODE_STILL_CAPTURE, add 2 combinations: one streaming, one still capture. 645 if (bokehCap.getMode() == 646 CaptureRequest.CONTROL_EXTENDED_SCENE_MODE_BOKEH_STILL_CAPTURE) { 647 CameraCombination combination2 = new CameraCombination( 648 index, bokehCap.getMode(), previewSize.getWidth(), 649 previewSize.getHeight(), mCameraId, 650 mModeNames.get(bokehCap.getMode()), 651 /*isStillCapture*/true); 652 653 if (!mTestedCombinations.contains(combination2)) { 654 mUntestedCombinations.add(combination2); 655 } 656 } 657 } 658 659 mJpegImageReader = ImageReader.newInstance( 660 mJpegSize.getWidth(), mJpegSize.getHeight(), ImageFormat.JPEG, 1); 661 mJpegImageReader.setOnImageAvailableListener(this, mCameraHandler); 662 663 setUntestedCombination(); 664 665 if (mPreviewTexture != null) { 666 startPreview(); 667 } 668 } 669 shutdownCamera()670 private void shutdownCamera() { 671 if (null != mCaptureSession) { 672 mCaptureSession.close(); 673 mCaptureSession = null; 674 } 675 if (null != mCameraDevice) { 676 mCameraDevice.close(); 677 mCameraDevice = null; 678 } 679 if (null != mJpegImageReader) { 680 mJpegImageReader.close(); 681 mJpegImageReader = null; 682 } 683 if (null != mYuvImageReader) { 684 mYuvImageReader.close(); 685 mYuvImageReader = null; 686 } 687 } 688 configurePreviewTextureTransform()689 private void configurePreviewTextureTransform() { 690 int rotation = getWindowManager().getDefaultDisplay().getRotation(); 691 Configuration config = getResources().getConfiguration(); 692 int degrees = 0; 693 switch (rotation) { 694 case Surface.ROTATION_0: degrees = 0; break; 695 case Surface.ROTATION_90: degrees = 90; break; 696 case Surface.ROTATION_180: degrees = 180; break; 697 case Surface.ROTATION_270: degrees = 270; break; 698 } 699 Matrix matrix = mPreviewView.getTransform(null); 700 int deviceOrientation = Configuration.ORIENTATION_PORTRAIT; 701 if ((degrees % 180 == 0 && config.orientation == Configuration.ORIENTATION_LANDSCAPE) || 702 (degrees % 180 == 90 && config.orientation == Configuration.ORIENTATION_PORTRAIT)) { 703 deviceOrientation = Configuration.ORIENTATION_LANDSCAPE; 704 } 705 int effectiveWidth = mPreviewSize.getWidth(); 706 int effectiveHeight = mPreviewSize.getHeight(); 707 if (deviceOrientation == Configuration.ORIENTATION_PORTRAIT) { 708 int temp = effectiveWidth; 709 effectiveWidth = effectiveHeight; 710 effectiveHeight = temp; 711 } 712 713 RectF viewRect = new RectF(0, 0, mPreviewTexWidth, mPreviewTexHeight); 714 RectF bufferRect = new RectF(0, 0, effectiveWidth, effectiveHeight); 715 float centerX = viewRect.centerX(); 716 float centerY = viewRect.centerY(); 717 bufferRect.offset(centerX - bufferRect.centerX(), 718 centerY - bufferRect.centerY()); 719 720 matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); 721 722 matrix.postRotate((360 - degrees) % 360, centerX, centerY); 723 if ((degrees % 180) == 90) { 724 int temp = effectiveWidth; 725 effectiveWidth = effectiveHeight; 726 effectiveHeight = temp; 727 } 728 // Scale to fit view, avoiding any crop 729 float scale = Math.min(mPreviewTexWidth / (float) effectiveWidth, 730 mPreviewTexHeight / (float) effectiveHeight); 731 matrix.postScale(scale, scale, centerX, centerY); 732 733 mPreviewView.setTransform(matrix); 734 } 735 /** 736 * Starts a background thread and its {@link Handler}. 737 */ startBackgroundThread()738 private void startBackgroundThread() { 739 mCameraThread = new HandlerThread("CameraBokehBackground"); 740 mCameraThread.start(); 741 mCameraHandler = new Handler(mCameraThread.getLooper()); 742 } 743 744 /** 745 * Stops the background thread and its {@link Handler}. 746 */ stopBackgroundThread()747 private void stopBackgroundThread() { 748 mCameraThread.quitSafely(); 749 try { 750 mCameraThread.join(); 751 mCameraThread = null; 752 mCameraHandler = null; 753 } catch (InterruptedException e) { 754 e.printStackTrace(); 755 } 756 } 757 startPreview()758 private void startPreview() { 759 try { 760 if (mPreviewSize == null || !mPreviewSize.equals(mNextCombination.mPreviewSize)) { 761 mPreviewSize = mNextCombination.mPreviewSize; 762 763 mYuvImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), 764 mPreviewSize.getHeight(), ImageFormat.YUV_420_888, 1); 765 mYuvImageReader.setOnImageAvailableListener(this, mCameraHandler); 766 }; 767 768 mPreviewTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); 769 mPreviewRequestBuilder = 770 mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 771 mPreviewRequestBuilder.addTarget(mPreviewSurface); 772 mPreviewRequestBuilder.addTarget(mYuvImageReader.getSurface()); 773 774 mStillCaptureRequestBuilder = 775 mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); 776 mStillCaptureRequestBuilder.addTarget(mPreviewSurface); 777 mStillCaptureRequestBuilder.addTarget(mJpegImageReader.getSurface()); 778 779 mSessionListener = new BlockingSessionCallback(); 780 List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/3); 781 outputSurfaces.add(mPreviewSurface); 782 outputSurfaces.add(mYuvImageReader.getSurface()); 783 outputSurfaces.add(mJpegImageReader.getSurface()); 784 mCameraDevice.createCaptureSession(outputSurfaces, mSessionListener, mCameraHandler); 785 mCaptureSession = mSessionListener.waitAndGetSession(/*timeoutMs*/3000); 786 787 configurePreviewTextureTransform(); 788 789 /* Set bokeh mode and start streaming */ 790 int bokehMode = mNextCombination.mMode; 791 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_EXTENDED_SCENE_MODE, bokehMode); 792 mStillCaptureRequestBuilder.set(CaptureRequest.CONTROL_EXTENDED_SCENE_MODE, bokehMode); 793 mPreviewRequest = mPreviewRequestBuilder.build(); 794 mStillCaptureRequest = mStillCaptureRequestBuilder.build(); 795 796 mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mCameraHandler); 797 } catch (CameraAccessException e) { 798 e.printStackTrace(); 799 } 800 } 801 takePicture()802 private void takePicture() { 803 try { 804 mCaptureSession.stopRepeating(); 805 mSessionListener.getStateWaiter().waitForState( 806 BlockingSessionCallback.SESSION_READY, SESSION_READY_TIMEOUT_MS); 807 808 mCaptureSession.capture(mStillCaptureRequest, mCaptureCallback, mCameraHandler); 809 } catch (CameraAccessException e) { 810 e.printStackTrace(); 811 } 812 } 813 setPassButtonEnabled(boolean enabled)814 private void setPassButtonEnabled(boolean enabled) { 815 ImageButton pass_button = (ImageButton) findViewById(R.id.pass_button); 816 pass_button.setEnabled(enabled); 817 } 818 } 819