1 /* 2 * Copyright 2014 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.example.android.powerprofile.cameraavg; 18 19 import android.Manifest; 20 import android.app.Activity; 21 import android.app.AlertDialog; 22 import android.app.Dialog; 23 import android.app.DialogFragment; 24 import android.app.Fragment; 25 import android.content.Context; 26 import android.content.DialogInterface; 27 import android.content.pm.PackageManager; 28 import android.content.res.Configuration; 29 import android.graphics.ImageFormat; 30 import android.graphics.Matrix; 31 import android.graphics.Point; 32 import android.graphics.RectF; 33 import android.graphics.SurfaceTexture; 34 import android.hardware.camera2.CameraAccessException; 35 import android.hardware.camera2.CameraCaptureSession; 36 import android.hardware.camera2.CameraCharacteristics; 37 import android.hardware.camera2.CameraDevice; 38 import android.hardware.camera2.CameraManager; 39 import android.hardware.camera2.CameraMetadata; 40 import android.hardware.camera2.CaptureRequest; 41 import android.hardware.camera2.CaptureResult; 42 import android.hardware.camera2.TotalCaptureResult; 43 import android.hardware.camera2.params.StreamConfigurationMap; 44 import android.media.Image; 45 import android.media.ImageReader; 46 import android.os.Bundle; 47 import android.os.Handler; 48 import android.os.HandlerThread; 49 import android.support.annotation.NonNull; 50 import android.support.v13.app.FragmentCompat; 51 import android.support.v4.content.ContextCompat; 52 import android.util.Log; 53 import android.util.Size; 54 import android.util.SparseIntArray; 55 import android.view.LayoutInflater; 56 import android.view.Surface; 57 import android.view.TextureView; 58 import android.view.View; 59 import android.view.ViewGroup; 60 import android.widget.Toast; 61 62 import java.io.File; 63 import java.io.FileOutputStream; 64 import java.io.IOException; 65 import java.nio.ByteBuffer; 66 import java.util.ArrayList; 67 import java.util.Arrays; 68 import java.util.Collections; 69 import java.util.Comparator; 70 import java.util.List; 71 import java.util.concurrent.Semaphore; 72 import java.util.concurrent.TimeUnit; 73 74 public class CameraAvgFragment extends Fragment 75 implements View.OnClickListener, FragmentCompat.OnRequestPermissionsResultCallback { 76 77 /** 78 * Conversion from screen rotation to JPEG orientation. 79 */ 80 private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); 81 private static final int REQUEST_CAMERA_PERMISSION = 1; 82 private static final String FRAGMENT_DIALOG = "dialog"; 83 84 static { ORIENTATIONS.append(Surface.ROTATION_0, 90)85 ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0)86 ORIENTATIONS.append(Surface.ROTATION_90, 0); ORIENTATIONS.append(Surface.ROTATION_180, 270)87 ORIENTATIONS.append(Surface.ROTATION_180, 270); ORIENTATIONS.append(Surface.ROTATION_270, 180)88 ORIENTATIONS.append(Surface.ROTATION_270, 180); 89 } 90 91 /** 92 * Tag for the {@link Log}. 93 */ 94 private static final String TAG = "CameraAvgFragment"; 95 96 /** 97 * Camera state: Showing camera preview. 98 */ 99 private static final int STATE_PREVIEW = 0; 100 101 /** 102 * Camera state: Waiting for the focus to be locked. 103 */ 104 private static final int STATE_WAITING_LOCK = 1; 105 106 /** 107 * Camera state: Waiting for the exposure to be precapture state. 108 */ 109 private static final int STATE_WAITING_PRECAPTURE = 2; 110 111 /** 112 * Camera state: Waiting for the exposure state to be something other than precapture. 113 */ 114 private static final int STATE_WAITING_NON_PRECAPTURE = 3; 115 116 /** 117 * Camera state: Picture was taken. 118 */ 119 private static final int STATE_PICTURE_TAKEN = 4; 120 121 /** 122 * Max preview width that is guaranteed by Camera2 API 123 */ 124 private static final int MAX_PREVIEW_WIDTH = 1920; 125 126 /** 127 * Max preview height that is guaranteed by Camera2 API 128 */ 129 private static final int MAX_PREVIEW_HEIGHT = 1080; 130 131 /** 132 * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a 133 * {@link TextureView}. 134 */ 135 private final TextureView.SurfaceTextureListener mSurfaceTextureListener 136 = new TextureView.SurfaceTextureListener() { 137 138 @Override 139 public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { 140 openCamera(width, height); 141 } 142 143 @Override 144 public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { 145 configureTransform(width, height); 146 } 147 148 @Override 149 public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { 150 return true; 151 } 152 153 @Override 154 public void onSurfaceTextureUpdated(SurfaceTexture texture) { 155 } 156 157 }; 158 159 /** 160 * ID of the current {@link CameraDevice}. 161 */ 162 private String mCameraId; 163 164 /** 165 * An {@link AutoFitTextureView} for camera preview. 166 */ 167 private AutoFitTextureView mTextureView; 168 169 /** 170 * A {@link CameraCaptureSession } for camera preview. 171 */ 172 private CameraCaptureSession mCaptureSession; 173 174 /** 175 * A reference to the opened {@link CameraDevice}. 176 */ 177 private CameraDevice mCameraDevice; 178 179 /** 180 * The {@link Size} of camera preview. 181 */ 182 private Size mPreviewSize; 183 184 private Runnable mCaptureRunnable; 185 186 /** 187 * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state. 188 */ 189 private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { 190 191 @Override 192 public void onOpened(@NonNull CameraDevice cameraDevice) { 193 // This method is called when the camera is opened. We start camera preview here. 194 mCameraOpenCloseLock.release(); 195 mCameraDevice = cameraDevice; 196 createCameraPreviewSession(); 197 } 198 199 @Override 200 public void onDisconnected(@NonNull CameraDevice cameraDevice) { 201 mCameraOpenCloseLock.release(); 202 cameraDevice.close(); 203 mCameraDevice = null; 204 } 205 206 @Override 207 public void onError(@NonNull CameraDevice cameraDevice, int error) { 208 mCameraOpenCloseLock.release(); 209 cameraDevice.close(); 210 mCameraDevice = null; 211 Activity activity = getActivity(); 212 if (null != activity && !activity.isFinishing()) { 213 activity.finish(); 214 } 215 } 216 217 }; 218 219 /** 220 * An additional thread for running tasks that shouldn't block the UI. 221 */ 222 private HandlerThread mBackgroundThread; 223 224 /** 225 * A {@link Handler} for running tasks in the background. 226 */ 227 private Handler mBackgroundHandler; 228 229 /** 230 * An {@link ImageReader} that handles still image capture. 231 */ 232 private ImageReader mImageReader; 233 234 /** 235 * This is the output file for our picture. 236 */ 237 private File mFile; 238 239 /** 240 * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a 241 * still image is ready to be saved. 242 */ 243 private final ImageReader.OnImageAvailableListener mOnImageAvailableListener 244 = new ImageReader.OnImageAvailableListener() { 245 246 @Override 247 public void onImageAvailable(ImageReader reader) { 248 mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile)); 249 } 250 }; 251 252 /** 253 * {@link CaptureRequest.Builder} for the camera preview 254 */ 255 private CaptureRequest.Builder mPreviewRequestBuilder; 256 257 /** 258 * {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder} 259 */ 260 private CaptureRequest mPreviewRequest; 261 262 /** 263 * The current state of camera state for taking pictures. 264 * 265 * @see #mCaptureCallback 266 */ 267 private int mState = STATE_PREVIEW; 268 269 /** 270 * A {@link Semaphore} to prevent the app from exiting before closing the camera. 271 */ 272 private Semaphore mCameraOpenCloseLock = new Semaphore(1); 273 274 /** 275 * Whether the current camera device supports Flash or not. 276 */ 277 private boolean mFlashSupported; 278 279 /** 280 * Orientation of the camera sensor 281 */ 282 private int mSensorOrientation; 283 284 /** 285 * A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture. 286 */ 287 private CameraCaptureSession.CaptureCallback mCaptureCallback 288 = new CameraCaptureSession.CaptureCallback() { 289 290 private void process(CaptureResult result) { 291 switch (mState) { 292 case STATE_PREVIEW: { 293 // We have nothing to do when the camera preview is working normally. 294 break; 295 } 296 case STATE_WAITING_LOCK: { 297 Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); 298 if (afState == null) { 299 captureStillPicture(); 300 } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || 301 CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) { 302 // CONTROL_AE_STATE can be null on some devices 303 Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 304 if (aeState == null || 305 aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { 306 mState = STATE_PICTURE_TAKEN; 307 captureStillPicture(); 308 } else { 309 runPrecaptureSequence(); 310 } 311 } 312 break; 313 } 314 case STATE_WAITING_PRECAPTURE: { 315 // CONTROL_AE_STATE can be null on some devices 316 Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 317 if (aeState == null || 318 aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || 319 aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { 320 mState = STATE_WAITING_NON_PRECAPTURE; 321 } 322 break; 323 } 324 case STATE_WAITING_NON_PRECAPTURE: { 325 // CONTROL_AE_STATE can be null on some devices 326 Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 327 if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { 328 mState = STATE_PICTURE_TAKEN; 329 captureStillPicture(); 330 } 331 break; 332 } 333 } 334 } 335 336 @Override 337 public void onCaptureProgressed(@NonNull CameraCaptureSession session, 338 @NonNull CaptureRequest request, 339 @NonNull CaptureResult partialResult) { 340 process(partialResult); 341 } 342 343 @Override 344 public void onCaptureCompleted(@NonNull CameraCaptureSession session, 345 @NonNull CaptureRequest request, 346 @NonNull TotalCaptureResult result) { 347 process(result); 348 } 349 350 }; 351 352 /** 353 * Shows a {@link Toast} on the UI thread. 354 * 355 * @param text The message to show 356 */ showToast(final String text)357 private void showToast(final String text) { 358 final Activity activity = getActivity(); 359 if (activity != null) { 360 activity.runOnUiThread(new Runnable() { 361 @Override 362 public void run() { 363 Toast.makeText(activity, text, Toast.LENGTH_SHORT).show(); 364 } 365 }); 366 } 367 } 368 369 /** 370 * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that 371 * is at least as large as the respective texture view size, and that is at most as large as the 372 * respective max size, and whose aspect ratio matches with the specified value. If such size 373 * doesn't exist, choose the largest one that is at most as large as the respective max size, 374 * and whose aspect ratio matches with the specified value. 375 * 376 * @param choices The list of sizes that the camera supports for the intended output 377 * class 378 * @param textureViewWidth The width of the texture view relative to sensor coordinate 379 * @param textureViewHeight The height of the texture view relative to sensor coordinate 380 * @param maxWidth The maximum width that can be chosen 381 * @param maxHeight The maximum height that can be chosen 382 * @param aspectRatio The aspect ratio 383 * @return The optimal {@code Size}, or an arbitrary one if none were big enough 384 */ chooseOptimalSize(Size[] choices, int textureViewWidth, int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio)385 private static Size chooseOptimalSize(Size[] choices, int textureViewWidth, 386 int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) { 387 388 // Collect the supported resolutions that are at least as big as the preview Surface 389 List<Size> bigEnough = new ArrayList<>(); 390 // Collect the supported resolutions that are smaller than the preview Surface 391 List<Size> notBigEnough = new ArrayList<>(); 392 int w = aspectRatio.getWidth(); 393 int h = aspectRatio.getHeight(); 394 for (Size option : choices) { 395 if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight && 396 option.getHeight() == option.getWidth() * h / w) { 397 if (option.getWidth() >= textureViewWidth && 398 option.getHeight() >= textureViewHeight) { 399 bigEnough.add(option); 400 } else { 401 notBigEnough.add(option); 402 } 403 } 404 } 405 406 // Pick the smallest of those big enough. If there is no one big enough, pick the 407 // largest of those not big enough. 408 if (bigEnough.size() > 0) { 409 return Collections.min(bigEnough, new CompareSizesByArea()); 410 } else if (notBigEnough.size() > 0) { 411 return Collections.max(notBigEnough, new CompareSizesByArea()); 412 } else { 413 Log.e(TAG, "Couldn't find any suitable preview size"); 414 return choices[0]; 415 } 416 } 417 newInstance()418 public static CameraAvgFragment newInstance() { 419 return new CameraAvgFragment(); 420 } 421 422 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)423 public View onCreateView(LayoutInflater inflater, ViewGroup container, 424 Bundle savedInstanceState) { 425 return inflater.inflate(R.layout.fragment_camera_avg, container, false); 426 } 427 428 @Override onViewCreated(final View view, Bundle savedInstanceState)429 public void onViewCreated(final View view, Bundle savedInstanceState) { 430 view.findViewById(R.id.picture).setOnClickListener(this); 431 view.findViewById(R.id.info).setOnClickListener(this); 432 mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture); 433 } 434 435 @Override onActivityCreated(Bundle savedInstanceState)436 public void onActivityCreated(Bundle savedInstanceState) { 437 super.onActivityCreated(savedInstanceState); 438 mFile = new File(getActivity().getExternalFilesDir(null), "pic.jpg"); 439 } 440 441 @Override onResume()442 public void onResume() { 443 super.onResume(); 444 startBackgroundThread(); 445 446 // When the screen is turned off and turned back on, the SurfaceTexture is already 447 // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open 448 // a camera and start preview from here (otherwise, we wait until the surface is ready in 449 // the SurfaceTextureListener). 450 if (mTextureView.isAvailable()) { 451 openCamera(mTextureView.getWidth(), mTextureView.getHeight()); 452 } else { 453 mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); 454 } 455 456 mCaptureRunnable = new Runnable() { 457 public void run() { 458 mBackgroundHandler.postDelayed(this, 6000); 459 CameraAvgFragment.this.takePicture(); 460 } 461 }; 462 mBackgroundHandler.postDelayed(mCaptureRunnable, 6000); 463 } 464 465 @Override onPause()466 public void onPause() { 467 mBackgroundHandler.removeCallbacks(mCaptureRunnable); 468 469 closeCamera(); 470 stopBackgroundThread(); 471 super.onPause(); 472 } 473 requestCameraPermission()474 private void requestCameraPermission() { 475 if (FragmentCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) { 476 new ConfirmationDialog().show(getChildFragmentManager(), FRAGMENT_DIALOG); 477 } else { 478 FragmentCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 479 REQUEST_CAMERA_PERMISSION); 480 } 481 } 482 483 @Override onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)484 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 485 @NonNull int[] grantResults) { 486 if (requestCode == REQUEST_CAMERA_PERMISSION) { 487 if (grantResults.length != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) { 488 ErrorDialog.newInstance(getString(R.string.request_permission)) 489 .show(getChildFragmentManager(), FRAGMENT_DIALOG); 490 } 491 } else { 492 super.onRequestPermissionsResult(requestCode, permissions, grantResults); 493 } 494 } 495 496 /** 497 * Sets up member variables related to camera. 498 * 499 * @param width The width of available size for camera preview 500 * @param height The height of available size for camera preview 501 */ setUpCameraOutputs(int width, int height)502 private void setUpCameraOutputs(int width, int height) { 503 Activity activity = getActivity(); 504 CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); 505 try { 506 for (String cameraId : manager.getCameraIdList()) { 507 CameraCharacteristics characteristics 508 = manager.getCameraCharacteristics(cameraId); 509 510 // We don't use a front facing camera in this sample. 511 Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); 512 if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) { 513 continue; 514 } 515 516 StreamConfigurationMap map = characteristics.get( 517 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 518 if (map == null) { 519 continue; 520 } 521 522 // For still image captures, we use the largest available size. 523 Size largest = Collections.max( 524 Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), 525 new CompareSizesByArea()); 526 mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), 527 ImageFormat.JPEG, /*maxImages*/2); 528 mImageReader.setOnImageAvailableListener( 529 mOnImageAvailableListener, mBackgroundHandler); 530 531 // Find out if we need to swap dimension to get the preview size relative to sensor 532 // coordinate. 533 int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 534 //noinspection ConstantConditions 535 mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); 536 boolean swappedDimensions = false; 537 switch (displayRotation) { 538 case Surface.ROTATION_0: 539 case Surface.ROTATION_180: 540 if (mSensorOrientation == 90 || mSensorOrientation == 270) { 541 swappedDimensions = true; 542 } 543 break; 544 case Surface.ROTATION_90: 545 case Surface.ROTATION_270: 546 if (mSensorOrientation == 0 || mSensorOrientation == 180) { 547 swappedDimensions = true; 548 } 549 break; 550 default: 551 Log.e(TAG, "Display rotation is invalid: " + displayRotation); 552 } 553 554 Point displaySize = new Point(); 555 activity.getWindowManager().getDefaultDisplay().getSize(displaySize); 556 int rotatedPreviewWidth = width; 557 int rotatedPreviewHeight = height; 558 int maxPreviewWidth = displaySize.x; 559 int maxPreviewHeight = displaySize.y; 560 561 if (swappedDimensions) { 562 rotatedPreviewWidth = height; 563 rotatedPreviewHeight = width; 564 maxPreviewWidth = displaySize.y; 565 maxPreviewHeight = displaySize.x; 566 } 567 568 if (maxPreviewWidth > MAX_PREVIEW_WIDTH) { 569 maxPreviewWidth = MAX_PREVIEW_WIDTH; 570 } 571 572 if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) { 573 maxPreviewHeight = MAX_PREVIEW_HEIGHT; 574 } 575 576 // Danger, W.R.! Attempting to use too large a preview size could exceed the camera 577 // bus' bandwidth limitation, resulting in gorgeous previews but the storage of 578 // garbage capture data. 579 mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), 580 rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth, 581 maxPreviewHeight, largest); 582 583 // We fit the aspect ratio of TextureView to the size of preview we picked. 584 int orientation = getResources().getConfiguration().orientation; 585 if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 586 mTextureView.setAspectRatio( 587 mPreviewSize.getWidth(), mPreviewSize.getHeight()); 588 } else { 589 mTextureView.setAspectRatio( 590 mPreviewSize.getHeight(), mPreviewSize.getWidth()); 591 } 592 593 // Check if the flash is supported. 594 Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); 595 mFlashSupported = available == null ? false : available; 596 597 mCameraId = cameraId; 598 return; 599 } 600 } catch (CameraAccessException e) { 601 e.printStackTrace(); 602 } catch (NullPointerException e) { 603 // Currently an NPE is thrown when the Camera2API is used but not supported on the 604 // device this code runs. 605 ErrorDialog.newInstance(getString(R.string.camera_error)) 606 .show(getChildFragmentManager(), FRAGMENT_DIALOG); 607 } 608 } 609 610 /** 611 * Opens the camera specified by {@link CameraAvgFragment#mCameraId}. 612 */ openCamera(int width, int height)613 private void openCamera(int width, int height) { 614 if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) 615 != PackageManager.PERMISSION_GRANTED) { 616 requestCameraPermission(); 617 return; 618 } 619 setUpCameraOutputs(width, height); 620 configureTransform(width, height); 621 Activity activity = getActivity(); 622 CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); 623 try { 624 if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { 625 throw new RuntimeException("Time out waiting to lock camera opening."); 626 } 627 manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler); 628 } catch (CameraAccessException e) { 629 e.printStackTrace(); 630 } catch (InterruptedException e) { 631 throw new RuntimeException("Interrupted while trying to lock camera opening.", e); 632 } 633 } 634 635 /** 636 * Closes the current {@link CameraDevice}. 637 */ closeCamera()638 private void closeCamera() { 639 try { 640 mCameraOpenCloseLock.acquire(); 641 if (null != mCaptureSession) { 642 mCaptureSession.close(); 643 mCaptureSession = null; 644 } 645 if (null != mCameraDevice) { 646 mCameraDevice.close(); 647 mCameraDevice = null; 648 } 649 if (null != mImageReader) { 650 mImageReader.close(); 651 mImageReader = null; 652 } 653 } catch (InterruptedException e) { 654 throw new RuntimeException("Interrupted while trying to lock camera closing.", e); 655 } finally { 656 mCameraOpenCloseLock.release(); 657 } 658 } 659 660 /** 661 * Starts a background thread and its {@link Handler}. 662 */ startBackgroundThread()663 private void startBackgroundThread() { 664 mBackgroundThread = new HandlerThread("CameraBackground"); 665 mBackgroundThread.start(); 666 mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); 667 } 668 669 /** 670 * Stops the background thread and its {@link Handler}. 671 */ stopBackgroundThread()672 private void stopBackgroundThread() { 673 mBackgroundThread.quitSafely(); 674 try { 675 mBackgroundThread.join(); 676 mBackgroundThread = null; 677 mBackgroundHandler = null; 678 } catch (InterruptedException e) { 679 e.printStackTrace(); 680 } 681 } 682 683 /** 684 * Creates a new {@link CameraCaptureSession} for camera preview. 685 */ createCameraPreviewSession()686 private void createCameraPreviewSession() { 687 try { 688 SurfaceTexture texture = mTextureView.getSurfaceTexture(); 689 assert texture != null; 690 691 // We configure the size of default buffer to be the size of camera preview we want. 692 texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); 693 694 // This is the output Surface we need to start preview. 695 Surface surface = new Surface(texture); 696 697 // We set up a CaptureRequest.Builder with the output Surface. 698 mPreviewRequestBuilder 699 = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 700 mPreviewRequestBuilder.addTarget(surface); 701 702 // Here, we create a CameraCaptureSession for camera preview. 703 mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), 704 new CameraCaptureSession.StateCallback() { 705 706 @Override 707 public void onConfigured( 708 @NonNull CameraCaptureSession cameraCaptureSession) { 709 // The camera is already closed 710 if (null == mCameraDevice) { 711 return; 712 } 713 714 // When the session is ready, we start displaying the preview. 715 mCaptureSession = cameraCaptureSession; 716 try { 717 // Auto focus should be continuous for camera preview. 718 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, 719 CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 720 // Flash is automatically enabled when necessary. 721 setAutoFlash(mPreviewRequestBuilder); 722 723 // Finally, we start displaying the camera preview. 724 mPreviewRequest = mPreviewRequestBuilder.build(); 725 mCaptureSession.setRepeatingRequest(mPreviewRequest, 726 mCaptureCallback, mBackgroundHandler); 727 } catch (CameraAccessException e) { 728 e.printStackTrace(); 729 } 730 } 731 732 @Override 733 public void onConfigureFailed( 734 @NonNull CameraCaptureSession cameraCaptureSession) { 735 showToast("Failed"); 736 } 737 }, null 738 ); 739 } catch (CameraAccessException e) { 740 e.printStackTrace(); 741 } 742 } 743 744 /** 745 * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`. 746 * This method should be called after the camera preview size is determined in 747 * setUpCameraOutputs and also the size of `mTextureView` is fixed. 748 * 749 * @param viewWidth The width of `mTextureView` 750 * @param viewHeight The height of `mTextureView` 751 */ configureTransform(int viewWidth, int viewHeight)752 private void configureTransform(int viewWidth, int viewHeight) { 753 Activity activity = getActivity(); 754 if (null == mTextureView || null == mPreviewSize || null == activity) { 755 return; 756 } 757 int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 758 Matrix matrix = new Matrix(); 759 RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); 760 RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth()); 761 float centerX = viewRect.centerX(); 762 float centerY = viewRect.centerY(); 763 if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { 764 bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); 765 matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); 766 float scale = Math.max( 767 (float) viewHeight / mPreviewSize.getHeight(), 768 (float) viewWidth / mPreviewSize.getWidth()); 769 matrix.postScale(scale, scale, centerX, centerY); 770 matrix.postRotate(90 * (rotation - 2), centerX, centerY); 771 } else if (Surface.ROTATION_180 == rotation) { 772 matrix.postRotate(180, centerX, centerY); 773 } 774 mTextureView.setTransform(matrix); 775 } 776 777 /** 778 * Initiate a still image capture. 779 */ takePicture()780 private void takePicture() { 781 lockFocus(); 782 } 783 784 /** 785 * Lock the focus as the first step for a still image capture. 786 */ lockFocus()787 private void lockFocus() { 788 try { 789 // This is how to tell the camera to lock focus. 790 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 791 CameraMetadata.CONTROL_AF_TRIGGER_START); 792 // Tell #mCaptureCallback to wait for the lock. 793 mState = STATE_WAITING_LOCK; 794 mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, 795 mBackgroundHandler); 796 } catch (CameraAccessException e) { 797 e.printStackTrace(); 798 } 799 } 800 801 /** 802 * Run the precapture sequence for capturing a still image. This method should be called when 803 * we get a response in {@link #mCaptureCallback} from {@link #lockFocus()}. 804 */ runPrecaptureSequence()805 private void runPrecaptureSequence() { 806 try { 807 // This is how to tell the camera to trigger. 808 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, 809 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); 810 // Tell #mCaptureCallback to wait for the precapture sequence to be set. 811 mState = STATE_WAITING_PRECAPTURE; 812 mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, 813 mBackgroundHandler); 814 } catch (CameraAccessException e) { 815 e.printStackTrace(); 816 } 817 } 818 819 /** 820 * Capture a still picture. This method should be called when we get a response in 821 * {@link #mCaptureCallback} from {@link #lockFocus()}. 822 */ captureStillPicture()823 private void captureStillPicture() { 824 try { 825 final Activity activity = getActivity(); 826 if (null == activity || null == mCameraDevice) { 827 return; 828 } 829 // This is the CaptureRequest.Builder that we use to take a picture. 830 final CaptureRequest.Builder captureBuilder = 831 mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); 832 captureBuilder.addTarget(mImageReader.getSurface()); 833 834 // Use the same AE and AF modes as the preview. 835 captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, 836 CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 837 setAutoFlash(captureBuilder); 838 839 // Orientation 840 int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 841 captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); 842 843 CameraCaptureSession.CaptureCallback CaptureCallback 844 = new CameraCaptureSession.CaptureCallback() { 845 846 @Override 847 public void onCaptureCompleted(@NonNull CameraCaptureSession session, 848 @NonNull CaptureRequest request, 849 @NonNull TotalCaptureResult result) { 850 Log.d(TAG, mFile.toString()); 851 unlockFocus(); 852 } 853 }; 854 855 mCaptureSession.stopRepeating(); 856 mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null); 857 } catch (CameraAccessException e) { 858 e.printStackTrace(); 859 } 860 } 861 862 /** 863 * Retrieves the JPEG orientation from the specified screen rotation. 864 * 865 * @param rotation The screen rotation. 866 * @return The JPEG orientation (one of 0, 90, 270, and 360) 867 */ getOrientation(int rotation)868 private int getOrientation(int rotation) { 869 // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) 870 // We have to take that into account and rotate JPEG properly. 871 // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS. 872 // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. 873 return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360; 874 } 875 876 /** 877 * Unlock the focus. This method should be called when still image capture sequence is 878 * finished. 879 */ unlockFocus()880 private void unlockFocus() { 881 try { 882 // Reset the auto-focus trigger 883 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 884 CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); 885 setAutoFlash(mPreviewRequestBuilder); 886 mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, 887 mBackgroundHandler); 888 // After this, the camera will go back to the normal state of preview. 889 mState = STATE_PREVIEW; 890 mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, 891 mBackgroundHandler); 892 } catch (CameraAccessException e) { 893 e.printStackTrace(); 894 } 895 } 896 897 @Override onClick(View view)898 public void onClick(View view) { 899 switch (view.getId()) { 900 case R.id.picture: { 901 takePicture(); 902 break; 903 } 904 case R.id.info: { 905 Activity activity = getActivity(); 906 if (null != activity) { 907 new AlertDialog.Builder(activity) 908 .setMessage(R.string.intro_message) 909 .setPositiveButton(android.R.string.ok, null) 910 .show(); 911 } 912 break; 913 } 914 } 915 } 916 setAutoFlash(CaptureRequest.Builder requestBuilder)917 private void setAutoFlash(CaptureRequest.Builder requestBuilder) { 918 if (mFlashSupported) { 919 requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, 920 CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); 921 } 922 } 923 924 /** 925 * Saves a JPEG {@link Image} into the specified {@link File}. 926 */ 927 private static class ImageSaver implements Runnable { 928 929 /** 930 * The JPEG image 931 */ 932 private final Image mImage; 933 /** 934 * The file we save the image into. 935 */ 936 private final File mFile; 937 ImageSaver(Image image, File file)938 public ImageSaver(Image image, File file) { 939 mImage = image; 940 mFile = file; 941 } 942 943 @Override run()944 public void run() { 945 ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); 946 byte[] bytes = new byte[buffer.remaining()]; 947 buffer.get(bytes); 948 FileOutputStream output = null; 949 try { 950 output = new FileOutputStream(mFile); 951 output.write(bytes); 952 } catch (IOException e) { 953 e.printStackTrace(); 954 } finally { 955 mImage.close(); 956 if (null != output) { 957 try { 958 output.close(); 959 } catch (IOException e) { 960 e.printStackTrace(); 961 } 962 } 963 } 964 } 965 966 } 967 968 /** 969 * Compares two {@code Size}s based on their areas. 970 */ 971 static class CompareSizesByArea implements Comparator<Size> { 972 973 @Override compare(Size lhs, Size rhs)974 public int compare(Size lhs, Size rhs) { 975 // We cast here to ensure the multiplications won't overflow 976 return Long.compare((long) lhs.getWidth() * lhs.getHeight(), 977 (long) rhs.getWidth() * rhs.getHeight()); 978 } 979 980 } 981 982 /** 983 * Shows an error message dialog. 984 */ 985 public static class ErrorDialog extends DialogFragment { 986 987 private static final String ARG_MESSAGE = "message"; 988 newInstance(String message)989 public static ErrorDialog newInstance(String message) { 990 ErrorDialog dialog = new ErrorDialog(); 991 Bundle args = new Bundle(); 992 args.putString(ARG_MESSAGE, message); 993 dialog.setArguments(args); 994 return dialog; 995 } 996 997 @Override onCreateDialog(Bundle savedInstanceState)998 public Dialog onCreateDialog(Bundle savedInstanceState) { 999 final Activity activity = getActivity(); 1000 return new AlertDialog.Builder(activity) 1001 .setMessage(getArguments().getString(ARG_MESSAGE)) 1002 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1003 @Override 1004 public void onClick(DialogInterface dialogInterface, int i) { 1005 if (activity != null && !activity.isFinishing()) { 1006 activity.finish(); 1007 } 1008 } 1009 }) 1010 .create(); 1011 } 1012 1013 } 1014 1015 /** 1016 * Shows OK/Cancel confirmation dialog about camera permission. 1017 */ 1018 public static class ConfirmationDialog extends DialogFragment { 1019 1020 @Override 1021 public Dialog onCreateDialog(Bundle savedInstanceState) { 1022 final Fragment parent = getParentFragment(); 1023 return new AlertDialog.Builder(getActivity()) 1024 .setMessage(R.string.request_permission) 1025 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1026 @Override 1027 public void onClick(DialogInterface dialog, int which) { 1028 FragmentCompat.requestPermissions(parent, 1029 new String[]{Manifest.permission.CAMERA}, 1030 REQUEST_CAMERA_PERMISSION); 1031 } 1032 }) 1033 .setNegativeButton(android.R.string.cancel, 1034 new DialogInterface.OnClickListener() { 1035 @Override 1036 public void onClick(DialogInterface dialog, int which) { 1037 Activity activity = parent.getActivity(); 1038 if (activity != null && !activity.isFinishing()) { 1039 activity.finish(); 1040 } 1041 } 1042 }) 1043 .create(); 1044 } 1045 } 1046 1047 } 1048