Camera deprecated in API 21 - android.hardware.camera2

#1

All,

Noticed upon trying this chapter with the latest API 21, that Camera was deprecated in this release.

http://developer.android.com/reference/android/hardware/camera2/package-summary.html

[quote]To enumerate, query, and open available camera devices, obtain a CameraManager instance.

Individual CameraDevices provide a set of static property information that describes the hardware device and the available settings and output parameters for the device. This information is provided through the CameraCharacteristics object, and is available through getCameraCharacteristics(String)[/quote]

I’m going to go through the chapter with the original Camera Intent, but may make a challenge for myself to rewrite this for the new CameraManager.

Has anyone else attempted this yet?

#2

Yes I’m working on this now. I wish the book was made with camera2 api because the api itself is pretty complex. However, if you go look at Camera2Basic you can take the example code and even hack it into your program. GL!

#3

I am stuck on this one too.

#4

I hate sharing hackish code, but you can take a look at what I did. I just hacked it enough to make it work:

CrimeCameraFragment.java

[code]public class CrimeCameraFragment extends Fragment implements View.OnClickListener {

private View mProgressContainer;
public static final String EXTRA_PHOTO_FILENAME = "com.x.x.criminalintent.photo_filename";

/**
 * Conversion from screen rotation to JPEG orientation.
 */
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();

static {
    ORIENTATIONS.append(Surface.ROTATION_0, 90);
    ORIENTATIONS.append(Surface.ROTATION_90, 0);
    ORIENTATIONS.append(Surface.ROTATION_180, 270);
    ORIENTATIONS.append(Surface.ROTATION_270, 180);
}

/**
 * Tag for the {@link Log}.
 */
private static final String TAG = "Camera2BasicFragment";

/**
 * Camera state: Showing camera preview.
 */
private static final int STATE_PREVIEW = 0;

/**
 * Camera state: Waiting for the focus to be locked.
 */
private static final int STATE_WAITING_LOCK = 1;
/**
 * Camera state: Waiting for the exposure to be precapture state.
 */
private static final int STATE_WAITING_PRECAPTURE = 2;
/**
 * Camera state: Waiting for the exposure state to be something other than precapture.
 */
private static final int STATE_WAITING_NON_PRECAPTURE = 3;
/**
 * Camera state: Picture was taken.
 */
private static final int STATE_PICTURE_TAKEN = 4;

/**
 * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a
 * {@link TextureView}.
 */
private final TextureView.SurfaceTextureListener mSurfaceTextureListener
        = new TextureView.SurfaceTextureListener() {

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
        openCamera(width, height);
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
        configureTransform(width, height);
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
        return true;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture texture) {
    }

};

/**
 * ID of the current {@link CameraDevice}.
 */
private String mCameraId;

/**
 * An {@link AutoFitTextureView} for camera preview.
 */
private AutoFitTextureView mTextureView;

/**
 * A {@link CameraCaptureSession } for camera preview.
 */

private CameraCaptureSession mCaptureSession;
/**
 * A reference to the opened {@link CameraDevice}.
 */

private CameraDevice mCameraDevice;
/**
 * The {@link android.util.Size} of camera preview.
 */

private Size mPreviewSize;

/**
 * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.
 */
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

    @Override
    public void onOpened(CameraDevice cameraDevice) {
        // This method is called when the camera is opened.  We start camera preview here.
        mCameraOpenCloseLock.release();
        mCameraDevice = cameraDevice;
        createCameraPreviewSession();
    }

    @Override
    public void onDisconnected(CameraDevice cameraDevice) {
        mCameraOpenCloseLock.release();
        cameraDevice.close();
        mCameraDevice = null;
    }

    @Override
    public void onError(CameraDevice cameraDevice, int error) {
        mCameraOpenCloseLock.release();
        cameraDevice.close();
        mCameraDevice = null;
        Activity activity = getActivity();
        if (null != activity) {
            activity.finish();
        }
    }

};

/**
 * An additional thread for running tasks that shouldn't block the UI.
 */
private HandlerThread mBackgroundThread;

/**
 * A {@link Handler} for running tasks in the background.
 */
private Handler mBackgroundHandler;

/**
 * An {@link ImageReader} that handles still image capture.
 */
private ImageReader mImageReader;

/**
 * This is the output file for our picture.
 */
private File mFile;

/**
 * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
 * still image is ready to be saved.
 */
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
        = new ImageReader.OnImageAvailableListener() {

    @Override
    public void onImageAvailable(ImageReader reader) {
        mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
    }

};

/**
 * {@link CaptureRequest.Builder} for the camera preview
 */
private CaptureRequest.Builder mPreviewRequestBuilder;

/**
 * {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder}
 */
private CaptureRequest mPreviewRequest;

/**
 * The current state of camera state for taking pictures.
 *
 * @see #mCaptureCallback
 */
private int mState = STATE_PREVIEW;

/**
 * A {@link Semaphore} to prevent the app from exiting before closing the camera.
 */
private Semaphore mCameraOpenCloseLock = new Semaphore(1);

/**
 * A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture.
 */
private CameraCaptureSession.CaptureCallback mCaptureCallback
        = new CameraCaptureSession.CaptureCallback() {

    private void process(CaptureResult result) {
        switch (mState) {
            case STATE_PREVIEW: {
                // We have nothing to do when the camera preview is working normally.
                break;
            }
            case STATE_WAITING_LOCK: {
                Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
                if (afState == null) {
                    captureStillPicture();
                } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
                        CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
                    // CONTROL_AE_STATE can be null on some devices
                    Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                    if (aeState == null ||
                            aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
                        mState = STATE_PICTURE_TAKEN;
                        captureStillPicture();
                    } else {
                        runPrecaptureSequence();
                    }
                }
                break;
            }
            case STATE_WAITING_PRECAPTURE: {
                // CONTROL_AE_STATE can be null on some devices
                Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                if (aeState == null ||
                        aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
                        aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
                    mState = STATE_WAITING_NON_PRECAPTURE;
                }
                break;
            }
            case STATE_WAITING_NON_PRECAPTURE: {
                // CONTROL_AE_STATE can be null on some devices
                Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
                    mState = STATE_PICTURE_TAKEN;
                    captureStillPicture();
                }
                break;
            }
        }
    }

    @Override
    public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
                                    CaptureResult partialResult) {
        process(partialResult);
    }

    @Override
    public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
                                   TotalCaptureResult result) {
        process(result);
    }

};

/**
 * A {@link Handler} for showing {@link Toast}s.
 */
private Handler mMessageHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        Activity activity = getActivity();
        if (activity != null) {
            Toast.makeText(activity, (String) msg.obj, Toast.LENGTH_SHORT).show();
        }
    }
};

/**
 * Shows a {@link Toast} on the UI thread.
 *
 * @param text The message to show
 */
private void showToast(String text) {
    // We show a Toast by sending request message to mMessageHandler. This makes sure that the
    // Toast is shown on the UI thread.
    Message message = Message.obtain();
    message.obj = text;
    mMessageHandler.sendMessage(message);
}

/**
 * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
 * width and height are at least as large as the respective requested values, and whose aspect
 * ratio matches with the specified value.
 *
 * @param choices     The list of sizes that the camera supports for the intended output class
 * @param width       The minimum desired width
 * @param height      The minimum desired height
 * @param aspectRatio The aspect ratio
 * @return The optimal {@code Size}, or an arbitrary one if none were big enough
 */
private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
    // Collect the supported resolutions that are at least as big as the preview Surface
    List<Size> bigEnough = new ArrayList<Size>();
    int w = aspectRatio.getWidth();
    int h = aspectRatio.getHeight();
    for (Size option : choices) {
        if (option.getHeight() == option.getWidth() * h / w &&
                option.getWidth() >= width && option.getHeight() >= height) {
            bigEnough.add(option);
        }
    }

    // Pick the smallest of those, assuming we found any
    if (bigEnough.size() > 0) {
        return Collections.min(bigEnough, new CompareSizesByArea());
    } else {
        Log.e(TAG, "Couldn't find any suitable preview size");
        return choices[0];
    }
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.fragment_crime_camera, container, false);

    mProgressContainer = v.findViewById(R.id.crime_camera_progressContainer);
    mProgressContainer.setVisibility(View.INVISIBLE);

    Button takePictureButton = (Button) v.findViewById(R.id.crime_camera_takePictureButton);
    takePictureButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            takePicture();
        }
    });

    mTextureView = (AutoFitTextureView) v.findViewById(R.id.crime_camera_texture);
    return v;
}

@Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mFile = new File(getActivity().getExternalFilesDir(null), UUID.randomUUID().toString() + "-pic.jpg");
}

@Override
public void onResume() {
    super.onResume();
    startBackgroundThread();

    // When the screen is turned off and turned back on, the SurfaceTexture is already
    // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
    // a camera and start preview from here (otherwise, we wait until the surface is ready in
    // the SurfaceTextureListener).
    if (mTextureView.isAvailable()) {
        openCamera(mTextureView.getWidth(), mTextureView.getHeight());
    } else {
        mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
    }
}

@Override
public void onPause() {
    closeCamera();
    stopBackgroundThread();
    super.onPause();
}

/**
 * Sets up member variables related to camera.
 *
 * @param width  The width of available size for camera preview
 * @param height The height of available size for camera preview
 */
private void setUpCameraOutputs(int width, int height) {
    Activity activity = getActivity();
    CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
    try {
        for (String cameraId : manager.getCameraIdList()) {
            CameraCharacteristics characteristics
                    = manager.getCameraCharacteristics(cameraId);

            // We don't use a front facing camera in this sample.
            if (characteristics.get(CameraCharacteristics.LENS_FACING)
                    == CameraCharacteristics.LENS_FACING_FRONT) {
                continue;
            }

            StreamConfigurationMap map = characteristics.get(
                    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

            // For still image captures, we use the largest available size.
            Size largest = Collections.max(
                    Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
                    new CompareSizesByArea());
            mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
                    ImageFormat.JPEG, /*maxImages*/2);
            mImageReader.setOnImageAvailableListener(
                    mOnImageAvailableListener, mBackgroundHandler);

            // Danger, W.R.! Attempting to use too large a preview size could  exceed the camera
            // bus' bandwidth limitation, resulting in gorgeous previews but the storage of
            // garbage capture data.
            mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
                    width, height, largest);

            // We fit the aspect ratio of TextureView to the size of preview we picked.
            int orientation = getResources().getConfiguration().orientation;
            if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                mTextureView.setAspectRatio(
                        mPreviewSize.getWidth(), mPreviewSize.getHeight());
            } else {
                mTextureView.setAspectRatio(
                        mPreviewSize.getHeight(), mPreviewSize.getWidth());
            }

            mCameraId = cameraId;
            return;
        }
    } catch (CameraAccessException e) {
        e.printStackTrace();
    } catch (NullPointerException e) {
        // Currently an NPE is thrown when the Camera2API is used but not supported on the
        // device this code runs.
        new ErrorDialog().show(getActivity().getFragmentManager(), "dialog");
    }
}

/**
 * Opens the camera specified by {@link //Camera2BasicFragment#mCameraId}.
 */
private void openCamera(int width, int height) {
    setUpCameraOutputs(width, height);
    configureTransform(width, height);
    Activity activity = getActivity();
    CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
    try {
        if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
            throw new RuntimeException("Time out waiting to lock camera opening.");
        }
        manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
    }
}

/**
 * Closes the current {@link CameraDevice}.
 */
private void closeCamera() {
    try {
        mCameraOpenCloseLock.acquire();
        if (null != mCaptureSession) {
            mCaptureSession.close();
            mCaptureSession = null;
        }
        if (null != mCameraDevice) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
        if (null != mImageReader) {
            mImageReader.close();
            mImageReader = null;
        }
    } catch (InterruptedException e) {
        throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
    } finally {
        mCameraOpenCloseLock.release();
    }
}

/**
 * Starts a background thread and its {@link Handler}.
 */
private void startBackgroundThread() {
    mBackgroundThread = new HandlerThread("CameraBackground");
    mBackgroundThread.start();
    mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}

/**
 * Stops the background thread and its {@link Handler}.
 */
private void stopBackgroundThread() {
    mBackgroundThread.quitSafely();
    try {
        mBackgroundThread.join();
        mBackgroundThread = null;
        mBackgroundHandler = null;
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

/**
 * Creates a new {@link CameraCaptureSession} for camera preview.
 */
private void createCameraPreviewSession() {
    try {
        SurfaceTexture texture = mTextureView.getSurfaceTexture();
        assert texture != null;

        // We configure the size of default buffer to be the size of camera preview we want.
        texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());

        // This is the output Surface we need to start preview.
        Surface surface = new Surface(texture);

        // We set up a CaptureRequest.Builder with the output Surface.
        mPreviewRequestBuilder
                = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        mPreviewRequestBuilder.addTarget(surface);

        // Here, we create a CameraCaptureSession for camera preview.
        mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
                new CameraCaptureSession.StateCallback() {

                    @Override
                    public void onConfigured(CameraCaptureSession cameraCaptureSession) {
                        // The camera is already closed
                        if (null == mCameraDevice) {
                            return;
                        }

                        // When the session is ready, we start displaying the preview.
                        mCaptureSession = cameraCaptureSession;
                        try {
                            // Auto focus should be continuous for camera preview.
                            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                            // Flash is automatically enabled when necessary.
                            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                                    CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);

                            // Finally, we start displaying the camera preview.
                            mPreviewRequest = mPreviewRequestBuilder.build();
                            mCaptureSession.setRepeatingRequest(mPreviewRequest,
                                    mCaptureCallback, mBackgroundHandler);
                        } catch (CameraAccessException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
                        showToast("Failed");
                    }
                }, null
        );
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

/**
 * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`.
 * This method should be called after the camera preview size is determined in
 * setUpCameraOutputs and also the size of `mTextureView` is fixed.
 *
 * @param viewWidth  The width of `mTextureView`
 * @param viewHeight The height of `mTextureView`
 */
private void configureTransform(int viewWidth, int viewHeight) {
    Activity activity = getActivity();
    if (null == mTextureView || null == mPreviewSize || null == activity) {
        return;
    }
    int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
    Matrix matrix = new Matrix();
    RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
    RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
    float centerX = viewRect.centerX();
    float centerY = viewRect.centerY();
    if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
        bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
        matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
        float scale = Math.max(
                (float) viewHeight / mPreviewSize.getHeight(),
                (float) viewWidth / mPreviewSize.getWidth());
        matrix.postScale(scale, scale, centerX, centerY);
        matrix.postRotate(90 * (rotation - 2), centerX, centerY);
    } else if (Surface.ROTATION_180 == rotation) {
        matrix.postRotate(180, centerX, centerY);
    }
    mTextureView.setTransform(matrix);
}

/**
 * Initiate a still image capture.
 */
private void takePicture() {
    mProgressContainer.setVisibility(View.VISIBLE);
    lockFocus();
}

/**
 * Lock the focus as the first step for a still image capture.
 */
private void lockFocus() {
    try {
        // This is how to tell the camera to lock focus.
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                CameraMetadata.CONTROL_AF_TRIGGER_START);
        // Tell #mCaptureCallback to wait for the lock.
        mState = STATE_WAITING_LOCK;
        mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                mBackgroundHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

/**
 * Run the precapture sequence for capturing a still image. This method should be called when we
 * get a response in {@link #mCaptureCallback} from {@link #lockFocus()}.
 */
private void runPrecaptureSequence() {
    try {
        // This is how to tell the camera to trigger.
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
        // Tell #mCaptureCallback to wait for the precapture sequence to be set.
        mState = STATE_WAITING_PRECAPTURE;
        mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                mBackgroundHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

/**
 * Capture a still picture. This method should be called when we get a response in
 * {@link #mCaptureCallback} from both {@link #lockFocus()}.
 */
private void captureStillPicture() {
    try {
        final Activity activity = getActivity();
        if (null == activity || null == mCameraDevice) {
            return;
        }
        // This is the CaptureRequest.Builder that we use to take a picture.
        final CaptureRequest.Builder captureBuilder =
                mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        captureBuilder.addTarget(mImageReader.getSurface());

        // Use the same AE and AF modes as the preview.
        captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
        captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);

        // Orientation
        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));

        CameraCaptureSession.CaptureCallback CaptureCallback
                = new CameraCaptureSession.CaptureCallback() {

            @Override
            public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
                                           TotalCaptureResult result) {
                showToast("Saved: " + mFile);
                Intent i = new Intent();
                Log.i(TAG, "what is the filename " + mFile.getName());
                i.putExtra(EXTRA_PHOTO_FILENAME, mFile.getName());
                getActivity().setResult(Activity.RESULT_OK, i);
                unlockFocus();
            }
        };

        mCaptureSession.stopRepeating();
        mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

/**
 * Unlock the focus. This method should be called when still image capture sequence is finished.
 */
private void unlockFocus() {
    getActivity().runOnUiThread(new Runnable() {
        @Override
        public void run() {

            mProgressContainer.setVisibility(View.INVISIBLE);

        }
    });

    try {
        // Reset the autofucos trigger
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
        mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                mBackgroundHandler);
        // After this, the camera will go back to the normal state of preview.
        mState = STATE_PREVIEW;
        mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback,
                mBackgroundHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

public void onClick(View view) {
    switch (view.getId()) {
        case R.id.picture: {
            takePicture();
            break;
        }
        case R.id.info: {
            Activity activity = getActivity();
            if (null != activity) {
                new AlertDialog.Builder(activity)
                        .setMessage(R.string.intro_message)
                        .setPositiveButton(android.R.string.ok, null)
                        .show();
            }
            break;
        }
    }
}

/**
 * Saves a JPEG {@link Image} into the specified {@link File}.
 */
private static class ImageSaver implements Runnable {

    /**
     * The JPEG image
     */
    private final Image mImage;
    /**
     * The file we save the image into.
     */
    private final File mFile;

    public ImageSaver(Image image, File file) {
        mImage = image;
        mFile = file;
    }

    @Override
    public void run() {
        ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
        byte[] bytes = new byte[buffer.remaining()];
        buffer.get(bytes);
        FileOutputStream output = null;
        try {
            output = new FileOutputStream(mFile);
            output.write(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            mImage.close();
            if (null != output) {
                try {
                    output.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

/**
 * Compares two {@code Size}s based on their areas.
 */
static class CompareSizesByArea implements Comparator<Size> {

    @Override
    public int compare(Size lhs, Size rhs) {
        // We cast here to ensure the multiplications won't overflow
        return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
                (long) rhs.getWidth() * rhs.getHeight());
    }

}

public static class ErrorDialog extends DialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        final Activity activity = getActivity();
        return new AlertDialog.Builder(activity)
                .setMessage("This device doesn't support Camera2 API.")
                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        activity.finish();
                    }
                })
                .create();
    }

    public void show(android.support.v4.app.FragmentManager supportFragmentManager, String dialog) {
        Log.e(dialog, "some error");
    }
}

}[/code]

fragment_crime_camera.xml

[code]<?xml version="1.0" encoding="utf-8"?>

<LinearLayout
    android:layout_width="match_parent"
    android:orientation="horizontal"
    android:layout_height="match_parent">

    <com.x.x.criminalintent.AutoFitTextureView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:id="@+id/crime_camera_texture"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:id="@+id/crime_camera_takePictureButton"
        android:text="@string/take"/>
</LinearLayout>

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/crime_camera_progressContainer"
    android:clickable="true">

    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        style="@android:style/Widget.ProgressBar.Large" />

</FrameLayout>

[/code]

You take the AutoFitTextureView class from the Camera2Basic example and I think that’s about it. Someone might come along and post a more elegant solution. I don’t imagine you need half the code from Camera2Basic.