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> <resources>
<string name="picture">Picture</string> <string name="picture">Picture</string>
<string name="description_info">Info</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> </resources>

View File

@@ -16,6 +16,7 @@
package com.example.android.camera2basic; package com.example.android.camera2basic;
import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
@@ -23,6 +24,7 @@ import android.app.DialogFragment;
import android.app.Fragment; import android.app.Fragment;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.ImageFormat; import android.graphics.ImageFormat;
import android.graphics.Matrix; import android.graphics.Matrix;
@@ -43,7 +45,8 @@ import android.media.ImageReader;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; 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.Log;
import android.util.Size; import android.util.Size;
import android.util.SparseIntArray; import android.util.SparseIntArray;
@@ -66,12 +69,15 @@ import java.util.List;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; 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. * Conversion from screen rotation to JPEG orientation.
*/ */
private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
private static final int REQUEST_CAMERA_PERMISSION = 1;
private static final String FRAGMENT_DIALOG = "dialog";
static { static {
ORIENTATIONS.append(Surface.ROTATION_0, 90); 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. * Camera state: Waiting for the focus to be locked.
*/ */
private static final int STATE_WAITING_LOCK = 1; private static final int STATE_WAITING_LOCK = 1;
/** /**
* Camera state: Waiting for the exposure to be precapture state. * Camera state: Waiting for the exposure to be precapture state.
*/ */
private static final int STATE_WAITING_PRECAPTURE = 2; private static final int STATE_WAITING_PRECAPTURE = 2;
/** /**
* Camera state: Waiting for the exposure state to be something other than precapture. * Camera state: Waiting for the exposure state to be something other than precapture.
*/ */
private static final int STATE_WAITING_NON_PRECAPTURE = 3; private static final int STATE_WAITING_NON_PRECAPTURE = 3;
/** /**
* Camera state: Picture was taken. * Camera state: Picture was taken.
*/ */
@@ -148,17 +157,16 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
/** /**
* A {@link CameraCaptureSession } for camera preview. * A {@link CameraCaptureSession } for camera preview.
*/ */
private CameraCaptureSession mCaptureSession; private CameraCaptureSession mCaptureSession;
/** /**
* A reference to the opened {@link CameraDevice}. * A reference to the opened {@link CameraDevice}.
*/ */
private CameraDevice mCameraDevice; private CameraDevice mCameraDevice;
/** /**
* The {@link android.util.Size} of camera preview. * The {@link android.util.Size} of camera preview.
*/ */
private Size mPreviewSize; private Size mPreviewSize;
/** /**
@@ -167,7 +175,7 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override @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. // This method is called when the camera is opened. We start camera preview here.
mCameraOpenCloseLock.release(); mCameraOpenCloseLock.release();
mCameraDevice = cameraDevice; mCameraDevice = cameraDevice;
@@ -175,14 +183,14 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
} }
@Override @Override
public void onDisconnected(CameraDevice cameraDevice) { public void onDisconnected(@NonNull CameraDevice cameraDevice) {
mCameraOpenCloseLock.release(); mCameraOpenCloseLock.release();
cameraDevice.close(); cameraDevice.close();
mCameraDevice = null; mCameraDevice = null;
} }
@Override @Override
public void onError(CameraDevice cameraDevice, int error) { public void onError(@NonNull CameraDevice cameraDevice, int error) {
mCameraOpenCloseLock.release(); mCameraOpenCloseLock.release();
cameraDevice.close(); cameraDevice.close();
mCameraDevice = null; mCameraDevice = null;
@@ -303,43 +311,36 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
} }
@Override @Override
public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, public void onCaptureProgressed(@NonNull CameraCaptureSession session,
CaptureResult partialResult) { @NonNull CaptureRequest request,
@NonNull CaptureResult partialResult) {
process(partialResult); process(partialResult);
} }
@Override @Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, public void onCaptureCompleted(@NonNull CameraCaptureSession session,
TotalCaptureResult result) { @NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
process(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. * Shows a {@link Toast} on the UI thread.
* *
* @param text The message to show * @param text The message to show
*/ */
private void showToast(String text) { private void showToast(final String text) {
// We show a Toast by sending request message to mMessageHandler. This makes sure that the final Activity activity = getActivity();
// Toast is shown on the UI thread. if (activity != null) {
Message message = Message.obtain(); activity.runOnUiThread(new Runnable() {
message.obj = text; @Override
mMessageHandler.sendMessage(message); 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) { 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 // 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 w = aspectRatio.getWidth();
int h = aspectRatio.getHeight(); int h = aspectRatio.getHeight();
for (Size option : choices) { for (Size option : choices) {
@@ -375,9 +376,7 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
} }
public static Camera2BasicFragment newInstance() { public static Camera2BasicFragment newInstance() {
Camera2BasicFragment fragment = new Camera2BasicFragment(); return new Camera2BasicFragment();
fragment.setRetainInstance(true);
return fragment;
} }
@Override @Override
@@ -422,6 +421,28 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
super.onPause(); 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. * Sets up member variables related to camera.
* *
@@ -437,13 +458,16 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
= manager.getCameraCharacteristics(cameraId); = manager.getCameraCharacteristics(cameraId);
// We don't use a front facing camera in this sample. // We don't use a front facing camera in this sample.
if (characteristics.get(CameraCharacteristics.LENS_FACING) Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
== CameraCharacteristics.LENS_FACING_FRONT) { if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
continue; continue;
} }
StreamConfigurationMap map = characteristics.get( StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
continue;
}
// For still image captures, we use the largest available size. // For still image captures, we use the largest available size.
Size largest = Collections.max( Size largest = Collections.max(
@@ -478,7 +502,8 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
} catch (NullPointerException e) { } catch (NullPointerException e) {
// Currently an NPE is thrown when the Camera2API is used but not supported on the // Currently an NPE is thrown when the Camera2API is used but not supported on the
// device this code runs. // 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}. * Opens the camera specified by {@link Camera2BasicFragment#mCameraId}.
*/ */
private void openCamera(int width, int height) { private void openCamera(int width, int height) {
if (getActivity().checkSelfPermission(Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
requestCameraPermission();
return;
}
setUpCameraOutputs(width, height); setUpCameraOutputs(width, height);
configureTransform(width, height); configureTransform(width, height);
Activity activity = getActivity(); Activity activity = getActivity();
@@ -574,7 +604,7 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
new CameraCaptureSession.StateCallback() { new CameraCaptureSession.StateCallback() {
@Override @Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) { public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
// The camera is already closed // The camera is already closed
if (null == mCameraDevice) { if (null == mCameraDevice) {
return; return;
@@ -600,7 +630,8 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
} }
@Override @Override
public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { public void onConfigureFailed(
@NonNull CameraCaptureSession cameraCaptureSession) {
showToast("Failed"); showToast("Failed");
} }
}, null }, 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 * Run the precapture sequence for capturing a still image. This method should be called when
* get a response in {@link #mCaptureCallback} from {@link #lockFocus()}. * we get a response in {@link #mCaptureCallback} from {@link #lockFocus()}.
*/ */
private void runPrecaptureSequence() { private void runPrecaptureSequence() {
try { try {
@@ -714,9 +745,11 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
= new CameraCaptureSession.CaptureCallback() { = new CameraCaptureSession.CaptureCallback() {
@Override @Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, public void onCaptureCompleted(@NonNull CameraCaptureSession session,
TotalCaptureResult result) { @NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
showToast("Saved: " + mFile); showToast("Saved: " + mFile);
Log.d(TAG, mFile.toString());
unlockFocus(); 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() { private void unlockFocus() {
try { try {
// Reset the autofucos trigger // Reset the auto-focus trigger
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, 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 { 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 @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity(); final Activity activity = getActivity();
return new AlertDialog.Builder(activity) 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() { .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialogInterface, int i) { 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> <resources>
<string name="picture">Picture</string> <string name="picture">Picture</string>
<string name="description_info">Info</string> <string name="description_info">Info</string>
<string name="request_permission">This app needs camera permission.</string>
</resources> </resources>

View File

@@ -16,6 +16,7 @@
package com.example.android.camera2raw; package com.example.android.camera2raw;
import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
@@ -23,6 +24,7 @@ import android.app.DialogFragment;
import android.app.Fragment; import android.app.Fragment;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat; import android.graphics.ImageFormat;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.RectF; import android.graphics.RectF;
@@ -52,6 +54,8 @@ import android.os.HandlerThread;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.v13.app.FragmentCompat;
import android.support.v4.app.ActivityCompat;
import android.util.Log; import android.util.Log;
import android.util.Size; import android.util.Size;
import android.util.SparseIntArray; 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. * 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: * In this example, the lifecycle of a single request to take a photo is:
* <ul> * <ul>
* <li> * <li>
@@ -113,7 +117,9 @@ import java.util.concurrent.atomic.AtomicInteger;
* </li> * </li>
* </ul> * </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. * 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); 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. * Timeout for the pre-capture sequence.
*/ */
@@ -264,9 +284,9 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
private Handler mBackgroundHandler; private Handler mBackgroundHandler;
/** /**
* A reference counted holder wrapping the {@link ImageReader} that handles JPEG image captures. * A reference counted holder wrapping the {@link ImageReader} that handles JPEG image
* This is used to allow us to clean up the {@link ImageReader} when all background tasks using * captures. This is used to allow us to clean up the {@link ImageReader} when all background
* its {@link Image}s have completed. * tasks using its {@link Image}s have completed.
*/ */
private RefCountedAutoCloseable<ImageReader> mJpegImageReader; private RefCountedAutoCloseable<ImageReader> mJpegImageReader;
@@ -310,8 +330,8 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
private int mState = STATE_CLOSED; private int mState = STATE_CLOSED;
/** /**
* Timer to use with pre-capture sequence to ensure a timely capture if 3A convergence is taking * Timer to use with pre-capture sequence to ensure a timely capture if 3A convergence is
* too long. * taking too long.
*/ */
private long mCaptureTimer; private long mCaptureTimer;
@@ -559,9 +579,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
}; };
public static Camera2RawFragment newInstance() { public static Camera2RawFragment newInstance() {
Camera2RawFragment fragment = new Camera2RawFragment(); return new Camera2RawFragment();
fragment.setRetainInstance(true);
return fragment;
} }
@Override @Override
@@ -620,6 +638,20 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
super.onPause(); 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 @Override
public void onClick(View view) { public void onClick(View view) {
switch (view.getId()) { switch (view.getId()) {
@@ -718,6 +750,10 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
if (!setUpCameraOutputs()) { if (!setUpCameraOutputs()) {
return; return;
} }
if (!hasAllPermissionsGranted()) {
requestCameraPermissions();
return;
}
Activity activity = getActivity(); Activity activity = getActivity();
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
@@ -744,6 +780,57 @@ 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}. * Closes the current {@link CameraDevice}.
*/ */
@@ -810,7 +897,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/** /**
* Creates a new {@link CameraCaptureSession} for camera preview. * Creates a new {@link CameraCaptureSession} for camera preview.
* * <p/>
* Call this only with {@link #mCameraStateLock} held. * Call this only with {@link #mCameraStateLock} held.
*/ */
private void createCameraPreviewSessionLocked() { private void createCameraPreviewSessionLocked() {
@@ -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 * Configure the given {@link CaptureRequest.Builder} to use auto-focus, auto-exposure, and
* auto-white-balance controls if available. * auto-white-balance controls if available.
* * <p/>
* Call this only with {@link #mCameraStateLock} held. * Call this only with {@link #mCameraStateLock} held.
* *
* @param builder the builder to configure. * @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`, * Configure the necessary {@link android.graphics.Matrix} transformation to `mTextureView`,
* and start/restart the preview capture session if necessary. * and start/restart the preview capture session if necessary.
* * <p/>
* This method should be called after the camera state has been initialized in * This method should be called after the camera state has been initialized in
* setUpCameraOutputs. * setUpCameraOutputs.
* *
@@ -1027,7 +1114,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/** /**
* Initiate a still image capture. * Initiate a still image capture.
* * <p/>
* This function sends a capture request that initiates a pre-capture sequence in our state * 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 * 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 * longer moving, waits for auto-exposure to choose a good exposure value, and waits for
@@ -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 * Send a capture request to the camera device that initiates a capture targeting the JPEG and
* RAW outputs. * RAW outputs.
* * <p/>
* Call this only with {@link #mCameraStateLock} held. * Call this only with {@link #mCameraStateLock} held.
*/ */
private void captureStillPictureLocked() { 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 * Called after a RAW/JPEG capture has completed; resets the AF trigger state for the
* pre-capture sequence. * pre-capture sequence.
* * <p/>
* Call this only with {@link #mCameraStateLock} held. * Call this only with {@link #mCameraStateLock} held.
*/ */
private void finishedCaptureLocked() { private void finishedCaptureLocked() {
@@ -1156,8 +1243,8 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
* thread. * thread.
* *
* @param pendingQueue the currently active requests. * @param pendingQueue the currently active requests.
* @param reader a reference counted wrapper containing an {@link ImageReader} from which to * @param reader a reference counted wrapper containing an {@link ImageReader} from which
* acquire an image. * to acquire an image.
*/ */
private void dequeueAndSaveImage(TreeMap<Integer, ImageSaver.ImageSaverBuilder> pendingQueue, private void dequeueAndSaveImage(TreeMap<Integer, ImageSaver.ImageSaverBuilder> pendingQueue,
RefCountedAutoCloseable<ImageReader> reader) { 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 * Runnable that saves an {@link Image} into the specified {@link File}, and updates
* {@link android.provider.MediaStore} to include the resulting file. * {@link android.provider.MediaStore} to include the resulting file.
* * <p/>
* This can be constructed through an {@link ImageSaverBuilder} as the necessary image and * This can be constructed through an {@link ImageSaverBuilder} as the necessary image and
* result information becomes available. * result information becomes available.
*/ */
@@ -1307,7 +1394,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/** /**
* Builder class for constructing {@link ImageSaver}s. * Builder class for constructing {@link ImageSaver}s.
* * <p/>
* This class is thread safe. * This class is thread safe.
*/ */
public static class ImageSaverBuilder { 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}. * Construct a new ImageSaverBuilder using the given {@link Context}.
*
* @param context a {@link Context} to for accessing the * @param context a {@link Context} to for accessing the
* {@link android.provider.MediaStore}. * {@link android.provider.MediaStore}.
*/ */
@@ -1440,6 +1528,7 @@ public class Camera2RawFragment extends Fragment implements View.OnClickListener
/** /**
* Wrap the given object. * Wrap the given object.
*
* @param object an object to wrap. * @param object an object to wrap.
*/ */
public RefCountedAutoCloseable(T object) { 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 * Rotation need to transform from the camera sensor orientation to the device's current
* orientation. * 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 * @param deviceOrientation the current device orientation relative to the native device
* orientation. * orientation.
* @return the total rotation from the sensor orientation to the current 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 * 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 * send an {@link ImageSaver} with the results from this request to a background thread to
* save a file. * save a file.
* * <p/>
* Call this only with {@link #mCameraStateLock} held. * Call this only with {@link #mCameraStateLock} held.
* *
* @param requestId the ID of the {@link CaptureRequest} to handle. * @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. * Check if we are using a device that only supports the LEGACY hardware level.
* * <p/>
* Call this only with {@link #mCameraStateLock} held. * Call this only with {@link #mCameraStateLock} held.
* *
* @return true if this is a legacy device. * @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. * Start the timer for the pre-capture sequence.
* * <p/>
* Call this only with {@link #mCameraStateLock} held. * Call this only with {@link #mCameraStateLock} held.
*/ */
private void startTimerLocked() { 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. * Check if the timer for the pre-capture sequence has been hit.
* * <p/>
* Call this only with {@link #mCameraStateLock} held. * Call this only with {@link #mCameraStateLock} held.
* *
* @return true if the timeout occurred. * @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; 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="record">Record</string>
<string name="stop">Stop</string> <string name="stop">Stop</string>
<string name="description_info">Info</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> </resources>

View File

@@ -16,6 +16,7 @@
package com.example.android.camera2video; package com.example.android.camera2video;
import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
@@ -23,6 +24,7 @@ import android.app.DialogFragment;
import android.app.Fragment; import android.app.Fragment;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.RectF; import android.graphics.RectF;
@@ -39,6 +41,9 @@ import android.media.MediaRecorder;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; 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.Log;
import android.util.Size; import android.util.Size;
import android.util.SparseIntArray; import android.util.SparseIntArray;
@@ -59,11 +64,19 @@ import java.util.List;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; 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 SparseIntArray ORIENTATIONS = new SparseIntArray();
private static final String TAG = "Camera2VideoFragment"; 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 { static {
ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_0, 90);
@@ -88,7 +101,8 @@ public class Camera2VideoFragment extends Fragment implements View.OnClickListen
private CameraDevice mCameraDevice; 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; private CameraCaptureSession mPreviewSession;
@@ -198,14 +212,12 @@ public class Camera2VideoFragment extends Fragment implements View.OnClickListen
}; };
public static Camera2VideoFragment newInstance() { public static Camera2VideoFragment newInstance() {
Camera2VideoFragment fragment = new Camera2VideoFragment(); return new Camera2VideoFragment();
fragment.setRetainInstance(true);
return fragment;
} }
/** /**
* In this sample, we choose a video size with 3x4 aspect ratio. Also, we don't use sizes larger * In this sample, we choose a video size with 3x4 aspect ratio. Also, we don't use sizes
* than 1080p, since MediaRecorder cannot handle such a high-resolution video. * larger than 1080p, since MediaRecorder cannot handle such a high-resolution video.
* *
* @param choices The list of available sizes * @param choices The list of available sizes
* @return The video size * @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`. * Tries to open a {@link CameraDevice}. The result is listened by `mStateCallback`.
*/ */
private void openCamera(int width, int height) { private void openCamera(int width, int height) {
if (!hasPermissionsGranted(VIDEO_PERMISSIONS)) {
requestVideoPermissions();
return;
}
final Activity activity = getActivity(); final Activity activity = getActivity();
if (null == activity || activity.isFinishing()) { if (null == activity || activity.isFinishing()) {
return; return;
} }
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
try { try {
Log.d(TAG, "tryAcquire");
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera opening."); 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) { } catch (NullPointerException e) {
// Currently an NPE is thrown when the Camera2API is used but not supported on the // Currently an NPE is thrown when the Camera2API is used but not supported on the
// device this code runs. // device this code runs.
new ErrorDialog().show(getFragmentManager(), "dialog"); ErrorDialog.newInstance(getString(R.string.camera_error))
.show(getChildFragmentManager(), FRAGMENT_DIALOG);
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera opening."); 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 { 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 @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity(); final Activity activity = getActivity();
return new AlertDialog.Builder(activity) 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() { .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialogInterface, int i) { 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();
}
}
} }