aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-prod (mdb) <android-build-team-robot@google.com>2021-02-05 21:27:02 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2021-02-05 21:27:02 +0000
commit92ffb254b24e2a33c10252f6e9aba41f71b3cc1d (patch)
tree44e63e52661f5c98d8f484b54e51c380bde5f71f
parent4fc196a66681f1a48ccd8f2c40186f0e155e1f3a (diff)
parente0f2005549ca5bbf6408d3793c84350691103936 (diff)
downloadsupport-snap-temp-L16000000807332640.tar.gz
Merge "Remove the validation of image saved location to improve the performance of taking picture" into snap-temp-L16000000807332640snap-temp-L16000000807332640
-rw-r--r--camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java45
-rw-r--r--camera/camera-core/src/main/java/androidx/camera/core/ImageSaveLocationValidator.java127
-rw-r--r--camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java4
-rw-r--r--camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/HuaweiMediaStoreLocationValidationQuirk.java51
-rw-r--r--camera/camera-core/src/test/java/androidx/camera/core/ImageSaveLocationValidatorTest.java214
5 files changed, 5 insertions, 436 deletions
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index a6c3b876d5f..442ea68b1b6 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -767,22 +767,6 @@ public final class ImageCapture extends UseCase {
* set, or {@link #setCropAspectRatio} is used, the image may be cropped before saving to
* disk which causes an additional latency.
*
- * <p> Before triggering the image capture pipeline, if the save location is a {@link File} or
- * {@link MediaStore}, it is first verified to ensure it's valid and writable. A {@link File}
- * is verified by attempting to open a {@link java.io.FileOutputStream} to it, whereas a
- * location in {@link MediaStore} is validated by
- * {@linkplain ContentResolver#insert(Uri, ContentValues) creating a new row} in the user
- * defined table, retrieving a {@link Uri} pointing to it, then attempting to open an
- * {@link OutputStream} to it. The newly created row is
- * {@linkplain ContentResolver#delete(Uri, String, String[]) deleted}
- * at the end of the verification. On Huawei devices, this deletion results in the system
- * displaying a notification informing the user that a photo has been deleted. In order to
- * avoid this, validating the image capture save location in
- * {@link android.provider.MediaStore} is skipped on Huawei devices.
- *
- * <p> If the validation of the save location fails, {@link OnImageSavedCallback}'s error
- * callback is invoked with an {@link ImageCaptureException}.
- *
* @param outputFileOptions Options to store the newly captured image.
* @param executor The executor in which the callback methods will be run.
* @param imageSavedCallback Callback to be called for the newly captured image.
@@ -792,30 +776,11 @@ public final class ImageCapture extends UseCase {
final @NonNull OutputFileOptions outputFileOptions,
final @NonNull Executor executor,
final @NonNull OnImageSavedCallback imageSavedCallback) {
- mSequentialIoExecutor.execute(() -> {
- if (!ImageSaveLocationValidator.isValid(outputFileOptions)) {
- // Check whether the captured image can be saved. If it cannot, fail fast and
- // notify user.
- executor.execute(() -> imageSavedCallback.onError(
- new ImageCaptureException(ERROR_FILE_IO,
- "Cannot save capture result to specified location", null)));
- } else {
- CameraXExecutors.mainThreadExecutor().execute(
- () -> takePictureAfterValidation(outputFileOptions, executor,
- imageSavedCallback));
- }
- });
- }
-
- /**
- * Takes picture after the output file options is validated.
- */
- @UiThread
- private void takePictureAfterValidation(
- final @NonNull OutputFileOptions outputFileOptions,
- final @NonNull Executor executor,
- final @NonNull OnImageSavedCallback imageSavedCallback) {
- Threads.checkMainThread();
+ if (Looper.getMainLooper() != Looper.myLooper()) {
+ CameraXExecutors.mainThreadExecutor().execute(
+ () -> takePicture(outputFileOptions, executor, imageSavedCallback));
+ return;
+ }
/*
* We need to chain the following callbacks to save the image to disk:
*
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageSaveLocationValidator.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageSaveLocationValidator.java
deleted file mode 100644
index d99aea708f2..00000000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageSaveLocationValidator.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.core;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.net.Uri;
-
-import androidx.annotation.NonNull;
-import androidx.camera.core.internal.compat.quirk.DeviceQuirks;
-import androidx.camera.core.internal.compat.quirk.HuaweiMediaStoreLocationValidationQuirk;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-class ImageSaveLocationValidator {
-
- private static final String TAG = "SaveLocationValidator";
-
- private ImageSaveLocationValidator() {
- }
-
- /**
- * Verifies whether the result of an image capture operation can be saved to the specified
- * location in {@link androidx.camera.core.ImageCapture.OutputFileOptions}.
- * <p>
- * This method checks whether an image capture to a {@link File} or to
- * {@link android.provider.MediaStore} will work by checking whether the {@link File} exists
- * and is writable, and if the {@link Uri} to {@link android.provider.MediaStore} is valid.
- *
- * @param outputFileOptions Options for saving the result of an image capture operation
- * @return true if the image capture result can be saved to the specified storage option,
- * false otherwise.
- */
- @SuppressWarnings("ConstantConditions")
- static boolean isValid(final @NonNull ImageCapture.OutputFileOptions outputFileOptions) {
- if (isSaveToFile(outputFileOptions)) {
- return canSaveToFile(outputFileOptions.getFile());
- }
-
- if (isSaveToMediaStore(outputFileOptions)) {
- // Skip verification on Huawei devices
- final HuaweiMediaStoreLocationValidationQuirk huaweiQuirk =
- DeviceQuirks.get(HuaweiMediaStoreLocationValidationQuirk.class);
- if (huaweiQuirk != null) {
- return huaweiQuirk.canSaveToMediaStore();
- }
-
- return canSaveToMediaStore(outputFileOptions.getContentResolver(),
- outputFileOptions.getSaveCollection(), outputFileOptions.getContentValues());
- }
- return true;
- }
-
- private static boolean isSaveToFile(
- final @NonNull ImageCapture.OutputFileOptions outputFileOptions) {
- return outputFileOptions.getFile() != null;
- }
-
- private static boolean isSaveToMediaStore(
- final @NonNull ImageCapture.OutputFileOptions outputFileOptions) {
- return outputFileOptions.getSaveCollection() != null
- && outputFileOptions.getContentResolver() != null
- && outputFileOptions.getContentValues() != null;
- }
-
- private static boolean canSaveToFile(@NonNull final File file) {
- // Try opening a write stream to the output file. If this succeeds, the image save
- // destination is valid. Otherwise, it's invalid.
- try (FileOutputStream ignored = new FileOutputStream(file)) {
- return true;
- } catch (IOException exception) {
- Logger.e(TAG, "Failed to open a write stream to " + file.toString(), exception);
- return false;
- }
- }
-
- private static boolean canSaveToMediaStore(@NonNull final ContentResolver contentResolver,
- @NonNull final Uri uri, @NonNull ContentValues values) {
- final Uri outputUri;
- try {
- // Get the uri where the image will be saved
- outputUri = contentResolver.insert(uri, values);
- } catch (IllegalArgumentException exception) {
- Logger.e(TAG, "Failed to insert into " + uri.toString(), exception);
- return false;
- }
-
- // If the uri is null, saving the capture result to the given uri isn't possible
- if (outputUri == null) {
- return false;
- }
-
- // Try opening a write stream to the output uri. If this succeeds, the image save
- // destination is valid. Otherwise, it's invalid.
- try (OutputStream stream = contentResolver.openOutputStream(outputUri)) {
- return stream != null;
- } catch (IOException exception) {
- Logger.e(TAG, "Failed to open a write stream to" + outputUri.toString(), exception);
- return false;
- } finally {
- try {
- // Delete inserted row
- contentResolver.delete(outputUri, null, null);
- } catch (IllegalArgumentException exception) {
- Logger.e(TAG, "Failed to delete inserted row at " + outputUri.toString(),
- exception);
- }
- }
- }
-}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java
index 54b9270c710..0a15ab7d60b 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java
@@ -39,10 +39,6 @@ public class DeviceQuirksLoader {
final List<Quirk> quirks = new ArrayList<>();
// Load all device specific quirks
- if (HuaweiMediaStoreLocationValidationQuirk.load()) {
- quirks.add(new HuaweiMediaStoreLocationValidationQuirk());
- }
-
if (IncompleteCameraListQuirk.load()) {
quirks.add(new IncompleteCameraListQuirk());
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/HuaweiMediaStoreLocationValidationQuirk.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/HuaweiMediaStoreLocationValidationQuirk.java
deleted file mode 100644
index 1a5d3acd51c..00000000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/HuaweiMediaStoreLocationValidationQuirk.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.core.internal.compat.quirk;
-
-import android.os.Build;
-
-import androidx.camera.core.impl.Quirk;
-
-/**
- * Quirk that displays a notification when an image in {@link android.provider.MediaStore} has
- * been deleted.
- *
- * <p> When triggering an image capture, the {@link androidx.camera.core.ImageCapture}
- * use case validates the save location before starting the image capture pipeline. Validating a
- * save location that is a {@link android.net.Uri} in {@link android.provider.MediaStore} involves
- * creating a new row in the user defined table, retrieving an output {@link android.net.Uri}
- * pointing to it, then attempting to open an {@link java.io.OutputStream} to it. The newly
- * created row is deleted at the end of the verification. On Huawei devices, this last step
- * results in the system displaying a notification informing the user that a photo has been
- * deleted. In order to avoid this, validating the image capture save location to
- * {@link android.provider.MediaStore} is skipped on Huawei devices. See b/169497925.
- */
-public class HuaweiMediaStoreLocationValidationQuirk implements Quirk {
-
- static boolean load() {
- return "HUAWEI".equals(Build.BRAND.toUpperCase())
- || "HONOR".equals(Build.BRAND.toUpperCase());
- }
-
- /**
- * Always skip checking if the image capture save destination in
- * {@link android.provider.MediaStore} is valid.
- */
- public boolean canSaveToMediaStore() {
- return true;
- }
-}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageSaveLocationValidatorTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageSaveLocationValidatorTest.java
deleted file mode 100644
index d2d095be40d..00000000000
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageSaveLocationValidatorTest.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.core;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assume.assumeFalse;
-
-import android.content.ContentProvider;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Build;
-import android.provider.MediaStore;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.annotation.internal.DoNotInstrument;
-import org.robolectric.util.ReflectionHelpers;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.function.Function;
-
-@RunWith(RobolectricTestRunner.class)
-@DoNotInstrument
-@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class ImageSaveLocationValidatorTest {
-
- private static final Uri ANY_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
-
- @Test
- public void canSaveToValidWritableFile() throws IOException {
- final File saveLocation = File.createTempFile("test", ".jpg");
- saveLocation.deleteOnExit();
- final ImageCapture.OutputFileOptions outputOptions =
- new ImageCapture.OutputFileOptions.Builder(saveLocation).build();
-
- assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isTrue();
- }
-
- @Test
- public void canSaveToFileInCache() {
- final File cacheDir = ApplicationProvider.getApplicationContext().getCacheDir();
- final String fileName = System.currentTimeMillis() + ".jpg";
- final File saveLocation = new File(cacheDir, fileName);
- saveLocation.deleteOnExit();
- final ImageCapture.OutputFileOptions outputOptions =
- new ImageCapture.OutputFileOptions.Builder(saveLocation).build();
-
- assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isTrue();
- }
-
- @Test
- public void cannotSaveToReadOnlyFile() throws IOException {
- final File saveLocation = File.createTempFile("test", ".jpg");
- saveLocation.setReadOnly();
- saveLocation.deleteOnExit();
- final ImageCapture.OutputFileOptions outputOptions =
- new ImageCapture.OutputFileOptions.Builder(saveLocation).build();
-
- assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isFalse();
- }
-
- @Test
- public void canSaveToMediaStore() {
- // Skip for Huawei devices
- assumeFalse(isHuaweiDevice());
-
- // Return a valid insertion Uri for the image
- TestContentProvider.sOnInsertCallback = uri -> Uri.parse(uri.toString() + "/1");
- Robolectric.buildContentProvider(TestContentProvider.class).create(ANY_URI.getAuthority());
-
- final ImageCapture.OutputFileOptions outputOptions = buildOutputFileOptions();
- assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isTrue();
- }
-
- @Test
- public void cannotSaveToMediaStore_providerFailsInsertion() {
- // Skip for Huawei devices
- assumeFalse(isHuaweiDevice());
-
- // Make the provider fail to return a valid insertion Uri
- TestContentProvider.sOnInsertCallback = uri -> null;
- Robolectric.buildContentProvider(TestContentProvider.class).create(ANY_URI.getAuthority());
-
- final ImageCapture.OutputFileOptions outputOptions = buildOutputFileOptions();
- assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isFalse();
- }
-
- @Test
- public void cannotSaveToMediaStore_providerCrashesOnInsertion() {
- // Skip for Huawei devices
- assumeFalse(isHuaweiDevice());
-
- // Make the provider crash when trying to return a valid insertion Uri
- TestContentProvider.sOnInsertCallback = uri -> {
- throw new IllegalArgumentException();
- };
- Robolectric.buildContentProvider(TestContentProvider.class).create(ANY_URI.getAuthority());
-
- final ImageCapture.OutputFileOptions outputOptions = buildOutputFileOptions();
- assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isFalse();
- }
-
- @Test
- public void canSaveToMediaStore_huaweiDevice() {
- ReflectionHelpers.setStaticField(Build.class, "BRAND", "huawei");
-
- // Validation should return `true` regardless of any errors that may result from storing
- // the image capture result in MediaStore, like the one below.
- TestContentProvider.sOnInsertCallback = uri -> null;
- Robolectric.buildContentProvider(TestContentProvider.class).create(ANY_URI.getAuthority());
-
- final ImageCapture.OutputFileOptions outputOptions = buildOutputFileOptions();
- assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isTrue();
- }
-
- @Test
- public void canSaveToMediaStore_honorDevice() {
- ReflectionHelpers.setStaticField(Build.class, "BRAND", "honor");
-
- // Validation should return `true` regardless of any errors that may result from storing
- // the image capture result in MediaStore, like the one below.
- TestContentProvider.sOnInsertCallback = uri -> null;
- Robolectric.buildContentProvider(TestContentProvider.class).create(ANY_URI.getAuthority());
-
- final ImageCapture.OutputFileOptions outputOptions = buildOutputFileOptions();
- assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isTrue();
- }
-
- @NonNull
- private ImageCapture.OutputFileOptions buildOutputFileOptions() {
- final Context context = ApplicationProvider.getApplicationContext();
- final ContentResolver resolver = context.getContentResolver();
- final ContentValues contentValues = new ContentValues();
- contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "test");
- contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
- return new ImageCapture.OutputFileOptions
- .Builder(resolver, ANY_URI, contentValues)
- .build();
- }
-
- private boolean isHuaweiDevice() {
- return "HUAWEI".equals(Build.BRAND.toUpperCase())
- || "HONOR".equals(Build.BRAND.toUpperCase());
- }
-
- static class TestContentProvider extends ContentProvider {
-
- @NonNull
- static Function<Uri, Uri> sOnInsertCallback = uri -> null;
-
- @Override
- public boolean onCreate() {
- return false;
- }
-
- @Nullable
- @Override
- public Cursor query(@NonNull Uri uri, @Nullable String[] projection,
- @Nullable String selection, @Nullable String[] selectionArgs,
- @Nullable String sortOrder) {
- return null;
- }
-
- @Nullable
- @Override
- public String getType(@NonNull Uri uri) {
- return null;
- }
-
- @Nullable
- @Override
- public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
- return sOnInsertCallback.apply(uri);
- }
-
- @Override
- public int delete(@NonNull Uri uri, @Nullable String selection,
- @Nullable String[] selectionArgs) {
- return 0;
- }
-
- @Override
- public int update(@NonNull Uri uri, @Nullable ContentValues values,
- @Nullable String selection, @Nullable String[] selectionArgs) {
- return 0;
- }
- }
-}