Sync sample prebuilts for mnc-docs

Synced to //developers/samples/android commit
7c1cbd61d1dea342322530bee4228d0d86bdeca1.

Change-Id: I682ebe15bb1d495866dbc8d43453cd7351aef5db
This commit is contained in:
Trevor Johns
2015-08-29 00:28:08 -07:00
parent fe105b4596
commit 8491f02e6e
6 changed files with 424 additions and 106 deletions

View File

@@ -16,4 +16,6 @@
<resources>
<string name="picture">Picture</string>
<string name="description_info">Info</string>
<string name="request_permission">This sample needs camera permission.</string>
<string name="camera_error">This device doesn\'t support Camera2 API.</string>
</resources>

View File

@@ -16,6 +16,7 @@
package com.example.android.camera2basic;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -23,6 +24,7 @@ import android.app.DialogFragment;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
@@ -43,7 +45,8 @@ import android.media.ImageReader;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.v13.app.FragmentCompat;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
@@ -66,12 +69,15 @@ import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class Camera2BasicFragment extends Fragment implements View.OnClickListener {
public class Camera2BasicFragment extends Fragment
implements View.OnClickListener, FragmentCompat.OnRequestPermissionsResultCallback {
/**
* Conversion from screen rotation to JPEG orientation.
*/
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
private static final int REQUEST_CAMERA_PERMISSION = 1;
private static final String FRAGMENT_DIALOG = "dialog";
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
@@ -94,14 +100,17 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
* 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.
*/
@@ -148,17 +157,16 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
/**
* 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;
/**
@@ -167,7 +175,7 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice cameraDevice) {
public void onOpened(@NonNull CameraDevice cameraDevice) {
// This method is called when the camera is opened. We start camera preview here.
mCameraOpenCloseLock.release();
mCameraDevice = cameraDevice;
@@ -175,14 +183,14 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
}
@Override
public void onDisconnected(CameraDevice cameraDevice) {
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
}
@Override
public void onError(CameraDevice cameraDevice, int error) {
public void onError(@NonNull CameraDevice cameraDevice, int error) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
@@ -303,43 +311,36 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
}
@Override
public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
CaptureResult partialResult) {
public void onCaptureProgressed(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull CaptureResult partialResult) {
process(partialResult);
}
@Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
TotalCaptureResult result) {
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull 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);
private void showToast(final String text) {
final Activity activity = getActivity();
if (activity != null) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, text, Toast.LENGTH_SHORT).show();
}
});
}
}
/**
@@ -355,7 +356,7 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
*/
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>();
List<Size> bigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices) {
@@ -375,9 +376,7 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
}
public static Camera2BasicFragment newInstance() {
Camera2BasicFragment fragment = new Camera2BasicFragment();
fragment.setRetainInstance(true);
return fragment;
return new Camera2BasicFragment();
}
@Override
@@ -422,6 +421,28 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
super.onPause();
}
private void requestCameraPermission() {
if (FragmentCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
new ConfirmationDialog().show(getChildFragmentManager(), FRAGMENT_DIALOG);
} else {
FragmentCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA_PERMISSION);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults.length != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
ErrorDialog.newInstance(getString(R.string.request_permission))
.show(getChildFragmentManager(), FRAGMENT_DIALOG);
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
/**
* Sets up member variables related to camera.
*
@@ -437,13 +458,16 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
= manager.getCameraCharacteristics(cameraId);
// We don't use a front facing camera in this sample.
if (characteristics.get(CameraCharacteristics.LENS_FACING)
== CameraCharacteristics.LENS_FACING_FRONT) {
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
continue;
}
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
continue;
}
// For still image captures, we use the largest available size.
Size largest = Collections.max(
@@ -478,7 +502,8 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
} 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(getFragmentManager(), "dialog");
ErrorDialog.newInstance(getString(R.string.camera_error))
.show(getChildFragmentManager(), FRAGMENT_DIALOG);
}
}
@@ -486,6 +511,11 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
* Opens the camera specified by {@link Camera2BasicFragment#mCameraId}.
*/
private void openCamera(int width, int height) {
if (getActivity().checkSelfPermission(Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
requestCameraPermission();
return;
}
setUpCameraOutputs(width, height);
configureTransform(width, height);
Activity activity = getActivity();
@@ -574,7 +604,7 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
// The camera is already closed
if (null == mCameraDevice) {
return;
@@ -600,7 +630,8 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
}
@Override
public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
public void onConfigureFailed(
@NonNull CameraCaptureSession cameraCaptureSession) {
showToast("Failed");
}
}, null
@@ -668,8 +699,8 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
}
/**
* 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()}.
* 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 {
@@ -714,9 +745,11 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
= new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
TotalCaptureResult result) {
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
showToast("Saved: " + mFile);
Log.d(TAG, mFile.toString());
unlockFocus();
}
};
@@ -729,11 +762,12 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
}
/**
* Unlock the focus. This method should be called when still image capture sequence is finished.
* Unlock the focus. This method should be called when still image capture sequence is
* finished.
*/
private void unlockFocus() {
try {
// Reset the autofucos trigger
// Reset the auto-focus trigger
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
@@ -827,13 +861,26 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
}
/**
* Shows an error message dialog.
*/
public static class ErrorDialog extends DialogFragment {
private static final String ARG_MESSAGE = "message";
public static ErrorDialog newInstance(String message) {
ErrorDialog dialog = new ErrorDialog();
Bundle args = new Bundle();
args.putString(ARG_MESSAGE, message);
dialog.setArguments(args);
return dialog;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
return new AlertDialog.Builder(activity)
.setMessage("This device doesn't support Camera2 API.")
.setMessage(getArguments().getString(ARG_MESSAGE))
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
@@ -845,4 +892,36 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
}
/**
* Shows OK/Cancel confirmation dialog about camera permission.
*/
public static class ConfirmationDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Fragment parent = getParentFragment();
return new AlertDialog.Builder(getActivity())
.setMessage(R.string.request_permission)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
FragmentCompat.requestPermissions(parent,
new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA_PERMISSION);
}
})
.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Activity activity = parent.getActivity();
if (activity != null) {
activity.finish();
}
}
})
.create();
}
}
}

View File

@@ -16,4 +16,5 @@
<resources>
<string name="picture">Picture</string>
<string name="description_info">Info</string>
<string name="request_permission">This app needs camera permission.</string>
</resources>

View File

@@ -16,6 +16,7 @@
package com.example.android.camera2raw;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -23,6 +24,7 @@ import android.app.DialogFragment;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.RectF;
@@ -52,6 +54,8 @@ import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.support.v13.app.FragmentCompat;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
@@ -84,7 +88,7 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* A fragment that demonstrates use of the Camera2 API to capture RAW and JPEG photos.
*
* <p/>
* In this example, the lifecycle of a single request to take a photo is:
* <ul>
* <li>
@@ -113,7 +117,9 @@ import java.util.concurrent.atomic.AtomicInteger;
* </li>
* </ul>
*/
public class Camera2RawFragment extends Fragment implements View.OnClickListener {
public class Camera2RawFragment extends Fragment
implements View.OnClickListener, FragmentCompat.OnRequestPermissionsResultCallback {
/**
* Conversion from screen rotation to JPEG orientation.
*/
@@ -126,6 +132,20 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
ORIENTATIONS.append(Surface.ROTATION_270, 270);
}
/**
* Request code for camera permissions.
*/
private static final int REQUEST_CAMERA_PERMISSIONS = 1;
/**
* Permissions required to take a picture.
*/
private static final String[] CAMERA_PERMISSIONS = {
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
};
/**
* Timeout for the pre-capture sequence.
*/
@@ -264,9 +284,9 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
private Handler mBackgroundHandler;
/**
* A reference counted holder wrapping the {@link ImageReader} that handles JPEG image captures.
* This is used to allow us to clean up the {@link ImageReader} when all background tasks using
* its {@link Image}s have completed.
* A reference counted holder wrapping the {@link ImageReader} that handles JPEG image
* captures. This is used to allow us to clean up the {@link ImageReader} when all background
* tasks using its {@link Image}s have completed.
*/
private RefCountedAutoCloseable<ImageReader> mJpegImageReader;
@@ -310,8 +330,8 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
private int mState = STATE_CLOSED;
/**
* Timer to use with pre-capture sequence to ensure a timely capture if 3A convergence is taking
* too long.
* Timer to use with pre-capture sequence to ensure a timely capture if 3A convergence is
* taking too long.
*/
private long mCaptureTimer;
@@ -352,7 +372,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
@Override
public void onError(CameraDevice cameraDevice, int error) {
Log.e(TAG, "Received camera device error: " + error);
synchronized(mCameraStateLock) {
synchronized (mCameraStateLock) {
mState = STATE_CLOSED;
mCameraOpenCloseLock.release();
cameraDevice.close();
@@ -402,7 +422,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
= new CameraCaptureSession.CaptureCallback() {
private void process(CaptureResult result) {
synchronized(mCameraStateLock) {
synchronized (mCameraStateLock) {
switch (mState) {
case STATE_PREVIEW: {
// We have nothing to do when the camera preview is running normally.
@@ -559,9 +579,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
};
public static Camera2RawFragment newInstance() {
Camera2RawFragment fragment = new Camera2RawFragment();
fragment.setRetainInstance(true);
return fragment;
return new Camera2RawFragment();
}
@Override
@@ -620,6 +638,20 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
super.onPause();
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == REQUEST_CAMERA_PERMISSIONS) {
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
showMissingPermissionError();
return;
}
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
@@ -676,7 +708,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
Arrays.asList(map.getOutputSizes(ImageFormat.RAW_SENSOR)),
new CompareSizesByArea());
synchronized(mCameraStateLock) {
synchronized (mCameraStateLock) {
// Set up ImageReaders for JPEG and RAW outputs. Place these in a reference
// counted wrapper to ensure they are only closed when all background tasks
// using them are finished.
@@ -718,6 +750,10 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
if (!setUpCameraOutputs()) {
return;
}
if (!hasAllPermissionsGranted()) {
requestCameraPermissions();
return;
}
Activity activity = getActivity();
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
@@ -744,13 +780,64 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
}
}
/**
* Requests permissions necessary to use camera and save pictures.
*/
private void requestCameraPermissions() {
if (shouldShowRationale()) {
PermissionConfirmationDialog.newInstance().show(getChildFragmentManager(), "dialog");
} else {
FragmentCompat.requestPermissions(this, CAMERA_PERMISSIONS, REQUEST_CAMERA_PERMISSIONS);
}
}
/**
* Tells whether all the necessary permissions are granted to this app.
*
* @return True if all the required permissions are granted.
*/
private boolean hasAllPermissionsGranted() {
for (String permission : CAMERA_PERMISSIONS) {
if (ActivityCompat.checkSelfPermission(getActivity(), permission)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* Gets whether you should show UI with rationale for requesting the permissions.
*
* @return True if the UI should be shown.
*/
private boolean shouldShowRationale() {
for (String permission : CAMERA_PERMISSIONS) {
if (FragmentCompat.shouldShowRequestPermissionRationale(this, permission)) {
return true;
}
}
return false;
}
/**
* Shows that this app really needs the permission and finishes the app.
*/
private void showMissingPermissionError() {
Activity activity = getActivity();
if (activity != null) {
Toast.makeText(activity, R.string.request_permission, Toast.LENGTH_SHORT).show();
activity.finish();
}
}
/**
* Closes the current {@link CameraDevice}.
*/
private void closeCamera() {
try {
mCameraOpenCloseLock.acquire();
synchronized(mCameraStateLock) {
synchronized (mCameraStateLock) {
// Reset state and clean up resources used by the camera.
// Note: After calling this, the ImageReaders will be closed after any background
@@ -787,7 +874,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
synchronized(mCameraStateLock) {
synchronized (mCameraStateLock) {
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
}
@@ -810,7 +897,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/**
* Creates a new {@link CameraCaptureSession} for camera preview.
*
* <p/>
* Call this only with {@link #mCameraStateLock} held.
*/
private void createCameraPreviewSessionLocked() {
@@ -846,7 +933,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
mPreviewRequestBuilder.build(),
mPreCaptureCallback, mBackgroundHandler);
mState = STATE_PREVIEW;
} catch (CameraAccessException|IllegalStateException e) {
} catch (CameraAccessException | IllegalStateException e) {
e.printStackTrace();
return;
}
@@ -869,7 +956,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/**
* Configure the given {@link CaptureRequest.Builder} to use auto-focus, auto-exposure, and
* auto-white-balance controls if available.
*
* <p/>
* Call this only with {@link #mCameraStateLock} held.
*
* @param builder the builder to configure.
@@ -923,7 +1010,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/**
* Configure the necessary {@link android.graphics.Matrix} transformation to `mTextureView`,
* and start/restart the preview capture session if necessary.
*
* <p/>
* This method should be called after the camera state has been initialized in
* setUpCameraOutputs.
*
@@ -932,7 +1019,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
*/
private void configureTransform(int viewWidth, int viewHeight) {
Activity activity = getActivity();
synchronized(mCameraStateLock) {
synchronized (mCameraStateLock) {
if (null == mTextureView || null == activity) {
return;
}
@@ -1027,14 +1114,14 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/**
* Initiate a still image capture.
*
* <p/>
* This function sends a capture request that initiates a pre-capture sequence in our state
* machine that waits for auto-focus to finish, ending in a "locked" state where the lens is no
* longer moving, waits for auto-exposure to choose a good exposure value, and waits for
* auto-white-balance to converge.
*/
private void takePicture() {
synchronized(mCameraStateLock) {
synchronized (mCameraStateLock) {
mPendingUserCaptures++;
// If we already triggered a pre-capture sequence, or are in a state where we cannot
@@ -1078,7 +1165,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/**
* Send a capture request to the camera device that initiates a capture targeting the JPEG and
* RAW outputs.
*
* <p/>
* Call this only with {@link #mCameraStateLock} held.
*/
private void captureStillPictureLocked() {
@@ -1127,7 +1214,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/**
* Called after a RAW/JPEG capture has completed; resets the AF trigger state for the
* pre-capture sequence.
*
* <p/>
* Call this only with {@link #mCameraStateLock} held.
*/
private void finishedCaptureLocked() {
@@ -1156,8 +1243,8 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
* thread.
*
* @param pendingQueue the currently active requests.
* @param reader a reference counted wrapper containing an {@link ImageReader} from which to
* acquire an image.
* @param reader a reference counted wrapper containing an {@link ImageReader} from which
* to acquire an image.
*/
private void dequeueAndSaveImage(TreeMap<Integer, ImageSaver.ImageSaverBuilder> pendingQueue,
RefCountedAutoCloseable<ImageReader> reader) {
@@ -1195,7 +1282,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/**
* Runnable that saves an {@link Image} into the specified {@link File}, and updates
* {@link android.provider.MediaStore} to include the resulting file.
*
* <p/>
* This can be constructed through an {@link ImageSaverBuilder} as the necessary image and
* result information becomes available.
*/
@@ -1245,7 +1332,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
public void run() {
boolean success = false;
int format = mImage.getFormat();
switch(format) {
switch (format) {
case ImageFormat.JPEG: {
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
@@ -1289,7 +1376,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
// If saving the file succeeded, update MediaStore.
if (success) {
MediaScannerConnection.scanFile(mContext, new String[] { mFile.getPath()},
MediaScannerConnection.scanFile(mContext, new String[]{mFile.getPath()},
/*mimeTypes*/null, new MediaScannerConnection.MediaScannerConnectionClient() {
@Override
public void onMediaScannerConnected() {
@@ -1307,7 +1394,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/**
* Builder class for constructing {@link ImageSaver}s.
*
* <p/>
* This class is thread safe.
*/
public static class ImageSaverBuilder {
@@ -1320,6 +1407,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/**
* Construct a new ImageSaverBuilder using the given {@link Context}.
*
* @param context a {@link Context} to for accessing the
* {@link android.provider.MediaStore}.
*/
@@ -1329,7 +1417,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
public synchronized ImageSaverBuilder setRefCountedReader(
RefCountedAutoCloseable<ImageReader> reader) {
if (reader == null ) throw new NullPointerException();
if (reader == null) throw new NullPointerException();
mReader = reader;
return this;
@@ -1440,6 +1528,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/**
* Wrap the given object.
*
* @param object an object to wrap.
*/
public RefCountedAutoCloseable(T object) {
@@ -1582,7 +1671,9 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/**
* Rotation need to transform from the camera sensor orientation to the device's current
* orientation.
* @param c the {@link CameraCharacteristics} to query for the camera sensor orientation.
*
* @param c the {@link CameraCharacteristics} to query for the camera sensor
* orientation.
* @param deviceOrientation the current device orientation relative to the native device
* orientation.
* @return the total rotation from the sensor orientation to the current device orientation.
@@ -1620,7 +1711,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
* If the given request has been completed, remove it from the queue of active requests and
* send an {@link ImageSaver} with the results from this request to a background thread to
* save a file.
*
* <p/>
* Call this only with {@link #mCameraStateLock} held.
*
* @param requestId the ID of the {@link CaptureRequest} to handle.
@@ -1639,7 +1730,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/**
* Check if we are using a device that only supports the LEGACY hardware level.
*
* <p/>
* Call this only with {@link #mCameraStateLock} held.
*
* @return true if this is a legacy device.
@@ -1651,7 +1742,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/**
* Start the timer for the pre-capture sequence.
*
* <p/>
* Call this only with {@link #mCameraStateLock} held.
*/
private void startTimerLocked() {
@@ -1660,7 +1751,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/**
* Check if the timer for the pre-capture sequence has been hit.
*
* <p/>
* Call this only with {@link #mCameraStateLock} held.
*
* @return true if the timeout occurred.
@@ -1669,6 +1760,37 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
return (SystemClock.elapsedRealtime() - mCaptureTimer) > PRECAPTURE_TIMEOUT_MS;
}
// *********************************************************************************************
/**
* A dialog that explains about the necessary permissions.
*/
public static class PermissionConfirmationDialog extends DialogFragment {
public static PermissionConfirmationDialog newInstance() {
return new PermissionConfirmationDialog();
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Fragment parent = getParentFragment();
return new AlertDialog.Builder(getActivity())
.setMessage(R.string.request_permission)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
FragmentCompat.requestPermissions(parent, CAMERA_PERMISSIONS,
REQUEST_CAMERA_PERMISSIONS);
}
})
.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
getActivity().finish();
}
})
.create();
}
}
}

View File

@@ -3,4 +3,6 @@
<string name="record">Record</string>
<string name="stop">Stop</string>
<string name="description_info">Info</string>
<string name="permission_request">This sample needs permission for camera and audio recording.</string>
<string name="camera_error">This device doesn\'t support Camera2 API.</string>
</resources>

View File

@@ -16,6 +16,7 @@
package com.example.android.camera2video;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -23,6 +24,7 @@ import android.app.DialogFragment;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.RectF;
@@ -39,6 +41,9 @@ import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.NonNull;
import android.support.v13.app.FragmentCompat;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
@@ -59,11 +64,19 @@ import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class Camera2VideoFragment extends Fragment implements View.OnClickListener {
public class Camera2VideoFragment extends Fragment
implements View.OnClickListener, FragmentCompat.OnRequestPermissionsResultCallback {
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
private static final String TAG = "Camera2VideoFragment";
private static final int REQUEST_VIDEO_PERMISSIONS = 1;
private static final String FRAGMENT_DIALOG = "dialog";
private static final String[] VIDEO_PERMISSIONS = {
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO,
};
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
@@ -88,7 +101,8 @@ public class Camera2VideoFragment extends Fragment implements View.OnClickListen
private CameraDevice mCameraDevice;
/**
* A reference to the current {@link android.hardware.camera2.CameraCaptureSession} for preview.
* A reference to the current {@link android.hardware.camera2.CameraCaptureSession} for
* preview.
*/
private CameraCaptureSession mPreviewSession;
@@ -198,14 +212,12 @@ public class Camera2VideoFragment extends Fragment implements View.OnClickListen
};
public static Camera2VideoFragment newInstance() {
Camera2VideoFragment fragment = new Camera2VideoFragment();
fragment.setRetainInstance(true);
return fragment;
return new Camera2VideoFragment();
}
/**
* In this sample, we choose a video size with 3x4 aspect ratio. Also, we don't use sizes larger
* than 1080p, since MediaRecorder cannot handle such a high-resolution video.
* In this sample, we choose a video size with 3x4 aspect ratio. Also, we don't use sizes
* larger than 1080p, since MediaRecorder cannot handle such a high-resolution video.
*
* @param choices The list of available sizes
* @return The video size
@@ -331,16 +343,79 @@ public class Camera2VideoFragment extends Fragment implements View.OnClickListen
}
}
/**
* Gets whether you should show UI with rationale for requesting permissions.
*
* @param permissions The permissions your app wants to request.
* @return Whether you can show permission rationale UI.
*/
private boolean shouldShowRequestPermissionRationale(String[] permissions) {
for (String permission : permissions) {
if (FragmentCompat.shouldShowRequestPermissionRationale(this, permission)) {
return true;
}
}
return false;
}
/**
* Requests permissions needed for recording video.
*/
private void requestVideoPermissions() {
if (shouldShowRequestPermissionRationale(VIDEO_PERMISSIONS)) {
new ConfirmationDialog().show(getChildFragmentManager(), FRAGMENT_DIALOG);
} else {
FragmentCompat.requestPermissions(this, VIDEO_PERMISSIONS, REQUEST_VIDEO_PERMISSIONS);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
Log.d(TAG, "onRequestPermissionsResult");
if (requestCode == REQUEST_VIDEO_PERMISSIONS) {
if (grantResults.length == VIDEO_PERMISSIONS.length) {
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
ErrorDialog.newInstance(getString(R.string.permission_request))
.show(getChildFragmentManager(), FRAGMENT_DIALOG);
break;
}
}
} else {
ErrorDialog.newInstance(getString(R.string.permission_request))
.show(getChildFragmentManager(), FRAGMENT_DIALOG);
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
private boolean hasPermissionsGranted(String[] permissions) {
for (String permission : permissions) {
if (ActivityCompat.checkSelfPermission(getActivity(), permission)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* Tries to open a {@link CameraDevice}. The result is listened by `mStateCallback`.
*/
private void openCamera(int width, int height) {
if (!hasPermissionsGranted(VIDEO_PERMISSIONS)) {
requestVideoPermissions();
return;
}
final Activity activity = getActivity();
if (null == activity || activity.isFinishing()) {
return;
}
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
try {
Log.d(TAG, "tryAcquire");
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera opening.");
}
@@ -369,7 +444,8 @@ public class Camera2VideoFragment extends Fragment implements View.OnClickListen
} 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(getFragmentManager(), "dialog");
ErrorDialog.newInstance(getString(R.string.camera_error))
.show(getChildFragmentManager(), FRAGMENT_DIALOG);
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera opening.");
}
@@ -559,11 +635,21 @@ public class Camera2VideoFragment extends Fragment implements View.OnClickListen
public static class ErrorDialog extends DialogFragment {
private static final String ARG_MESSAGE = "message";
public static ErrorDialog newInstance(String message) {
ErrorDialog dialog = new ErrorDialog();
Bundle args = new Bundle();
args.putString(ARG_MESSAGE, message);
dialog.setArguments(args);
return dialog;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
return new AlertDialog.Builder(activity)
.setMessage("This device doesn't support Camera2 API.")
.setMessage(getArguments().getString(ARG_MESSAGE))
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
@@ -575,4 +661,30 @@ public class Camera2VideoFragment extends Fragment implements View.OnClickListen
}
public static class ConfirmationDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Fragment parent = getParentFragment();
return new AlertDialog.Builder(getActivity())
.setMessage(R.string.permission_request)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
FragmentCompat.requestPermissions(parent, VIDEO_PERMISSIONS,
REQUEST_VIDEO_PERMISSIONS);
}
})
.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
parent.getActivity().finish();
}
})
.create();
}
}
}