V2 bug fixes (#356)

* Fix permissions error

* Fix #355

* Fix #357

* Improve CameraOptions tests
pull/360/head
Mattia Iavarone 6 years ago committed by GitHub
parent 7a5e0b33e4
commit a8a4e09900
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraOptions1Test.java
  2. 2
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java
  3. 2
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
  4. 11
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  5. 9
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java
  6. 13
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java
  7. 40
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  8. 7
      docs/_posts/2018-12-20-runtime-permissions.md

@ -130,6 +130,27 @@ public class CameraOptions1Test extends BaseTest {
} }
} }
@Test
public void testVideoSizesNull() {
// When videoSizes is null, we take the preview sizes.
List<Camera.Size> sizes = Arrays.asList(
mockCameraSize(100, 200),
mockCameraSize(50, 50),
mockCameraSize(1600, 900),
mockCameraSize(1000, 2000)
);
Camera.Parameters params = mock(Camera.Parameters.class);
when(params.getSupportedVideoSizes()).thenReturn(null);
when(params.getSupportedPreviewSizes()).thenReturn(sizes);
CameraOptions o = new CameraOptions(params, false);
Collection<Size> supportedSizes = o.getSupportedVideoSizes();
assertEquals(supportedSizes.size(), sizes.size());
for (Camera.Size size : sizes) {
Size internalSize = new Size(size.width, size.height);
assertTrue(supportedSizes.contains(internalSize));
}
}
@Test @Test
public void testVideoSizesFlip() { public void testVideoSizesFlip() {
List<Camera.Size> sizes = Arrays.asList( List<Camera.Size> sizes = Arrays.asList(

@ -62,7 +62,7 @@ public class CameraViewCallbacksTest extends BaseTest {
} }
@Override @Override
protected boolean checkPermissions(@NonNull Mode mode, @NonNull Audio audio) { protected boolean checkPermissions(@NonNull Audio audio) {
return true; return true;
} }
}; };

@ -50,7 +50,7 @@ public class CameraViewTest extends BaseTest {
} }
@Override @Override
protected boolean checkPermissions(@NonNull Mode mode, @NonNull Audio audio) { protected boolean checkPermissions(@NonNull Audio audio) {
return hasPermissions; return hasPermissions;
} }
}; };

@ -173,7 +173,16 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
Camera.Parameters params = mCamera.getParameters(); Camera.Parameters params = mCamera.getParameters();
mPreviewFormat = params.getPreviewFormat(); mPreviewFormat = params.getPreviewFormat();
params.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); // <- not allowed during preview params.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); // <- not allowed during preview
params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // <- allowed if (mMode == Mode.PICTURE) {
params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // <- allowed
} else {
// mCaptureSize in this case is a video size. The available video sizes are not necessarily
// a subset of the picture sizes, so we can't use the mCaptureSize value: it might crash.
// However, the setPictureSize() passed here is useless : we don't allow HQ pictures in video mode.
// While this might be lifted in the future, for now, just use a picture capture size.
Size pictureSize = computeCaptureSize(Mode.PICTURE);
params.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight());
}
mCamera.setParameters(params); mCamera.setParameters(params);
mCamera.setPreviewCallbackWithBuffer(null); // Release anything left mCamera.setPreviewCallbackWithBuffer(null); // Release anything left

@ -523,12 +523,17 @@ abstract class CameraController implements
@NonNull @NonNull
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
protected final Size computeCaptureSize() { protected final Size computeCaptureSize() {
return computeCaptureSize(mMode);
}
@SuppressWarnings("WeakerAccess")
protected final Size computeCaptureSize(Mode mode) {
// We want to pass stuff into the REF_VIEW reference, not the sensor one. // We want to pass stuff into the REF_VIEW reference, not the sensor one.
// This is already managed by CameraOptions, so we just flip again at the end. // This is already managed by CameraOptions, so we just flip again at the end.
boolean flip = flip(REF_SENSOR, REF_VIEW); boolean flip = flip(REF_SENSOR, REF_VIEW);
SizeSelector selector; SizeSelector selector;
Collection<Size> sizes; Collection<Size> sizes;
if (mMode == Mode.PICTURE) { if (mode == Mode.PICTURE) {
selector = mPictureSizeSelector; selector = mPictureSizeSelector;
sizes = mCameraOptions.getSupportedPictureSizes(); sizes = mCameraOptions.getSupportedPictureSizes();
} else { } else {
@ -538,7 +543,7 @@ abstract class CameraController implements
selector = SizeSelectors.or(selector, SizeSelectors.biggest()); selector = SizeSelectors.or(selector, SizeSelectors.biggest());
List<Size> list = new ArrayList<>(sizes); List<Size> list = new ArrayList<>(sizes);
Size result = selector.select(list).get(0); Size result = selector.select(list).get(0);
LOG.i("computeCaptureSize:", "result:", result, "flip:", flip); LOG.i("computeCaptureSize:", "result:", result, "flip:", flip, "mode:", mode);
if (flip) result = result.flip(); // Go back to REF_SENSOR if (flip) result = result.flip(); // Go back to REF_SENSOR
return result; return result;
} }

@ -87,7 +87,7 @@ public class CameraOptions {
exposureCorrectionSupported = params.getMinExposureCompensation() != 0 exposureCorrectionSupported = params.getMinExposureCompensation() != 0
|| params.getMaxExposureCompensation() != 0; || params.getMaxExposureCompensation() != 0;
// Sizes // Picture Sizes
List<Camera.Size> sizes = params.getSupportedPictureSizes(); List<Camera.Size> sizes = params.getSupportedPictureSizes();
for (Camera.Size size : sizes) { for (Camera.Size size : sizes) {
int width = flipSizes ? size.height : size.width; int width = flipSizes ? size.height : size.width;
@ -95,6 +95,8 @@ public class CameraOptions {
supportedPictureSizes.add(new Size(width, height)); supportedPictureSizes.add(new Size(width, height));
supportedPictureAspectRatio.add(AspectRatio.of(width, height)); supportedPictureAspectRatio.add(AspectRatio.of(width, height));
} }
// Video Sizes
List<Camera.Size> vsizes = params.getSupportedVideoSizes(); List<Camera.Size> vsizes = params.getSupportedVideoSizes();
if (vsizes != null) { if (vsizes != null) {
for (Camera.Size size : vsizes) { for (Camera.Size size : vsizes) {
@ -103,6 +105,15 @@ public class CameraOptions {
supportedVideoSizes.add(new Size(width, height)); supportedVideoSizes.add(new Size(width, height));
supportedVideoAspectRatio.add(AspectRatio.of(width, height)); supportedVideoAspectRatio.add(AspectRatio.of(width, height));
} }
} else {
// StackOverflow threads seems to agree that if getSupportedVideoSizes is null, previews can be used.
List<Camera.Size> fallback = params.getSupportedPreviewSizes();
for (Camera.Size size : fallback) {
int width = flipSizes ? size.height : size.width;
int height = flipSizes ? size.width : size.height;
supportedVideoSizes.add(new Size(width, height));
supportedVideoAspectRatio.add(AspectRatio.of(width, height));
}
} }
} }

@ -603,7 +603,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
if (!isEnabled()) return; if (!isEnabled()) return;
if (mCameraPreview != null) mCameraPreview.onResume(); if (mCameraPreview != null) mCameraPreview.onResume();
if (checkPermissions(getMode(), getAudio())) { if (checkPermissions(getAudio())) {
// Update display orientation for current CameraController // Update display orientation for current CameraController
mOrientationHelper.enable(getContext()); mOrientationHelper.enable(getContext());
mCameraController.setDisplayOffset(mOrientationHelper.getDisplayOffset()); mCameraController.setDisplayOffset(mOrientationHelper.getDisplayOffset());
@ -613,15 +613,15 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
/** /**
* Checks that we have appropriate permissions for this session type. * Checks that we have appropriate permissions.
* Throws if session = audio and manifest did not add the microphone permissions. * This means checking that we have audio permissions if audio = Audio.ON.
* @param mode the sessionType to be checked
* @param audio the audio setting to be checked * @param audio the audio setting to be checked
* @return true if we can go on, false otherwise. * @return true if we can go on, false otherwise.
*/ */
@SuppressWarnings("ConstantConditions")
@SuppressLint("NewApi") @SuppressLint("NewApi")
protected boolean checkPermissions(@NonNull Mode mode, @NonNull Audio audio) { protected boolean checkPermissions(@NonNull Audio audio) {
checkPermissionsManifestOrThrow(mode, audio); checkPermissionsManifestOrThrow(audio);
// Manifest is OK at this point. Let's check runtime permissions. // Manifest is OK at this point. Let's check runtime permissions.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true;
@ -641,12 +641,11 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
/** /**
* If mSessionType == SESSION_TYPE_VIDEO we will ask for RECORD_AUDIO permission. * If audio is on we will ask for RECORD_AUDIO permission.
* If the developer did not add this to its manifest, throw and fire warnings. * If the developer did not add this to its manifest, throw and fire warnings.
* (Hoping this is not caught elsewhere... we should test).
*/ */
private void checkPermissionsManifestOrThrow(@NonNull Mode mode, @NonNull Audio audio) { private void checkPermissionsManifestOrThrow(@NonNull Audio audio) {
if (mode == Mode.VIDEO && audio == Audio.ON) { if (audio == Audio.ON) {
try { try {
PackageManager manager = getContext().getPackageManager(); PackageManager manager = getContext().getPackageManager();
PackageInfo info = manager.getPackageInfo(getContext().getPackageName(), PackageManager.GET_PERMISSIONS); PackageInfo info = manager.getPackageInfo(getContext().getPackageName(), PackageManager.GET_PERMISSIONS);
@ -655,7 +654,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
return; return;
} }
} }
LOG.e("Permission error:", "When the session type is set to video,", LOG.e("Permission error:", "When audio is enabled (Audio.ON),",
"the RECORD_AUDIO permission should be added to the app manifest file."); "the RECORD_AUDIO permission should be added to the app manifest file.");
throw new IllegalStateException(CameraLogger.lastMessage); throw new IllegalStateException(CameraLogger.lastMessage);
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
@ -1027,7 +1026,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
// Check did took place, or will happen on start(). // Check did took place, or will happen on start().
mCameraController.setAudio(audio); mCameraController.setAudio(audio);
} else if (checkPermissions(getMode(), audio)) { } else if (checkPermissions(audio)) {
// Camera is running. Pass. // Camera is running. Pass.
mCameraController.setAudio(audio); mCameraController.setAudio(audio);
@ -1096,22 +1095,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
* @param mode desired session type. * @param mode desired session type.
*/ */
public void setMode(@NonNull Mode mode) { public void setMode(@NonNull Mode mode) {
mCameraController.setMode(mode);
if (mode == getMode() || isClosed()) {
// Check did took place, or will happen on start().
mCameraController.setMode(mode);
} else if (checkPermissions(mode, getAudio())) {
// Camera is running. CameraImpl setMode will do the trick.
mCameraController.setMode(mode);
} else {
// This means that the audio permission is being asked.
// Stop the camera so it can be restarted by the developer onPermissionResult.
// Developer must also set the session type again...
// Not ideal but good for now.
close();
}
} }

@ -41,8 +41,9 @@ device has cameras, and then start the camera view.
On Marshmallow+, the user must explicitly approve our permissions. You can On Marshmallow+, the user must explicitly approve our permissions. You can
- handle permissions yourself and then call `cameraView.start()` once they are acquired - handle permissions yourself and then call `open()` or `setLifecycleOwner()` once they are acquired
- or call `cameraView.start()` anyway: `CameraView` will present a permission request to the user based on - ignore this: `CameraView` will present a permission request to the user based on
whether they are needed or not with the current configuration. whether they are needed or not with the current configuration.
In the second case, you should restart the camera if you have a successful response from `onRequestPermissionResults()`. Note however, that this is done at the activity level, so the permission request callback
`onRequestPermissionResults()` will be invoked on the parent activity, not the fragment.
Loading…
Cancel
Save